Merge
authorDan Vanderkam <danvdk@gmail.com>
Sun, 23 Nov 2014 18:25:46 +0000 (13:25 -0500)
committerDan Vanderkam <danvdk@gmail.com>
Sun, 23 Nov 2014 18:25:46 +0000 (13:25 -0500)
93 files changed:
.gitignore
.jshintrc [new file with mode: 0644]
.travis.yml [new file with mode: 0644]
CONTRIBUTING.md [new file with mode: 0644]
Makefile
README
auto_tests/misc/local.html
auto_tests/misc/local.js
auto_tests/tests/callback.js
auto_tests/tests/date_ticker.js [new file with mode: 0644]
auto_tests/tests/dygraph-options-tests.js
auto_tests/tests/fast_canvas_proxy.js [new file with mode: 0644]
auto_tests/tests/fill_step_plot.js [new file with mode: 0644]
auto_tests/tests/hidpi.js [new file with mode: 0644]
auto_tests/tests/missing_points.js
auto_tests/tests/numeric_ticker.js
auto_tests/tests/per_series.js
auto_tests/tests/plugins.js
auto_tests/tests/range_selector.js
auto_tests/tests/selection.js
auto_tests/tests/simple_drawing.js
auto_tests/tests/smooth_plotter.js [new file with mode: 0644]
auto_tests/tests/stacked.js
auto_tests/tests/step_plot_per_series.js
auto_tests/tests/tickers.disabled-js [deleted file]
auto_tests/tests/to_dom_coords.js
auto_tests/tests/update_options.js
auto_tests/tests/utils_test.js
bower.json [new file with mode: 0644]
check-combined-unaffected.sh [new file with mode: 0755]
compile-with-closure.sh
dashed-canvas.js
datahandler/datahandler.js
docs/header.html
docs/legal.html
dygraph-canvas.js
dygraph-dev.js
dygraph-externs.js
dygraph-gviz.js
dygraph-interaction-model.js
dygraph-internal.externs.js
dygraph-layout.js
dygraph-options-reference.js
dygraph-options.js
dygraph-tickers.js
dygraph-types.js
dygraph-utils.js
dygraph.js
extras/smooth-plotter.js [new file with mode: 0644]
file-size-stats.sh [deleted file]
generate-combined.sh
generate-documentation.py
generate-download.py
jshint/CHANGELOG [deleted file]
jshint/Makefile [deleted file]
jshint/README.markdown [deleted file]
jshint/build/jshint-rhino.js [deleted file]
jshint/env/jsc.js [deleted file]
jshint/env/jsc.sh [deleted file]
jshint/env/rhino.js [deleted file]
jshint/env/wsh.js [deleted file]
jshint/jshint.js [deleted file]
lint.sh [deleted file]
package.json [new file with mode: 0644]
phantom-driver.js
plugins/axes.js
plugins/legend.js
plugins/range-selector.js
polyfills/console.js [new file with mode: 0644]
push-to-web.sh
release.sh
stacktrace.js [deleted file]
test.sh
tests/custom-circles.html
tests/dense-fill.html [new file with mode: 0644]
tests/drawing.html
tests/dygraph-many-points-benchmark.html
tests/fillGraph.html
tests/labelsDateUTC.html [new file with mode: 0644]
tests/linear-regression-addseries.html
tests/linear-regression-fractions.html
tests/linear-regression.html
tests/logscale.html
tests/per-series.html
tests/plotters.html
tests/plugins.html
tests/range-selector.html
tests/smooth-plots.html [new file with mode: 0644]
tests/steps.html
tests/two-axes-vr.html
tests/two-axes.html
tests/value-axis-formatters.html
tests/x-axis-formatter.html

index 159ec89..d6ddfb4 100644 (file)
@@ -1,2 +1,4 @@
 jsdoc
 docs/options.html
+node_modules
+env
diff --git a/.jshintrc b/.jshintrc
new file mode 100644 (file)
index 0000000..b50bc1f
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,18 @@
+{
+  "newcap": true,
+  "noarg": true,
+  "shadow": true,
+  "strict": true,
+  "forin": true,
+  "immed": true,
+  "latedef": true,
+  "nonbsp": true,
+  "undef": true,
+
+  "browser": true,
+  "devel": true,
+  "globals": {
+    "DEBUG": true
+  },
+  "maxerr": 100000
+}
diff --git a/.travis.yml b/.travis.yml
new file mode 100644 (file)
index 0000000..6eee1d0
--- /dev/null
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+
+script: "make travis"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644 (file)
index 0000000..3b5598b
--- /dev/null
@@ -0,0 +1,21 @@
+Filing a bug report? Please include the following:
+
+1. Link to a page which demonstrates the problem, preferably a jsfiddle (use [this jsfiddle](http://dygraphs.com/fiddle) as a template)
+2. Browser and Operating System that exhibit the problem
+
+It also helps if you include the non-compacted version of the JS on your
+page. For instance, instead of doing this:
+
+```html
+<script type="text/javascript" src="dygraph-combined.js"></script>
+```
+
+do this:
+
+```html
+<script type="text/javascript" src="dygraph-dev.js"></script>
+```
+
+This makes error messages and debugging simpler. The jsfiddle does this automatically.
+
+Sending a Pull Request? See our [guide to making dygraphs contributions](http://dygraphs.com/changes.html).
index e07bc1d..2ab31de 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,8 +1,4 @@
-# Run the generate-combined.sh script.
-# This Makefile isn't really necessary, but it serves as a "indicator"
-# to new users that they need to do a "build" of sorts.
-#
-# Dean Wampler <dean@deanwampler.com> March 22, 2010
+# You should run "npm install" before running any commands in this Makefile.
 
 all: test generate-combined generate-documentation
 
@@ -28,6 +24,7 @@ generate-gwt:
 
 test:
        @./test.sh
+       @./check-combined-unaffected.sh
 
 test-combined: move-combined test clean-combined-test
 
@@ -37,6 +34,17 @@ move-combined: generate-combined
 clean-combined-test: clean
        @echo restoring combined
        git checkout dygraph-dev.js
+       rm dygraph-combined.js.map
 
 lint:
-       @./lint.sh
+       @./generate-combined.sh ls \
+           | grep -v 'polyfills' \
+           | xargs ./node_modules/.bin/jshint
+
+# Commands to run for continuous integration on Travis-CI
+travis: test test-combined lint
+
+publish:
+       ./generate-combined.sh
+       npm publish
+       git checkout dygraph-combined.js
diff --git a/README b/README
index 53e19c5..cfd0e8c 100644 (file)
--- a/README
+++ b/README
@@ -3,9 +3,10 @@ Version 1.0.1
 Copyright (c) 2006-, Dan Vanderkam.
 
 Documentation: http://dygraphs.com/
-Support: http://groups.google.com/group/dygraphs-users
+Support: http://stackoverflow.com/questions/tagged/dygraphs
+         http://groups.google.com/group/dygraphs-users
 Source: http://github.com/danvk/dygraphs
-Issues: http://code.google.com/p/dygraphs/
+Issues: https://github.com/danvk/dygraphs/issues
 
 
 The dygraphs JavaScript library produces interactive, zoomable charts of time series.
@@ -52,16 +53,13 @@ dygraphs uses:
  - excanvas.js (Apache License)
  - YUI compressor (BSD License)
  - JsDoc Toolkit (MIT license)
- - stacktrace.js is public domain
+ - console-polyfill (MIT license)
 
 automated tests use:
  - auto_tests/lib/jquery-1.4.2.js (MIT & GPL2)
  - auto_tests/lib/Asserts.js (Apache 2.0 License)
  - auto-tests/lib/JsTestDriver-1.3.3cjar (Apache 2.0 License
 
-Linter uses:
- - JSHint (modified MIT license; prevents evil)
-
 excanvas: http://code.google.com/p/explorercanvas/
 yui compressor: http://developer.yahoo.com/yui/compressor/
 jsdoc toolkit: http://code.google.com/p/jsdoc-toolkit/
@@ -70,6 +68,4 @@ jquery: http://code.jquery.com/jquery-1.4.2.js
 Asserts.js: http://www.google.com/codesearch/p?#3tsINRJRCro/trunk/JsTestDriver/src/com/google/jstestdriver/javascript/Asserts.js
 JSTestDriver: http://code.google.com/p/js-test-driver/
 
-JSHint: jshint.com
-
 dygraphs is available under the MIT license, included in LICENSE.txt.
index f356fc5..e419959 100644 (file)
@@ -7,6 +7,7 @@
   <script type="text/javascript" src="../../excanvas.js"></script>
   <![endif]-->
   <script type="text/javascript" src="../../dygraph-dev.js"></script>
+  <script type="text/javascript" src="../../extras/smooth-plotter.js"></script>
 
   <!-- Scripts for library support -->
   <script type="text/javascript" src="../lib/jquery-1.4.2.js"></script>
   <script type="text/javascript" src="../tests/css.js"></script>
   <script type="text/javascript" src="../tests/custom_bars.js"></script>
   <script type="text/javascript" src="../tests/date_formats.js"></script>
+  <script type="text/javascript" src="../tests/date_ticker.js"></script>
   <script type="text/javascript" src="../tests/dygraph-options-tests.js"></script>
   <script type="text/javascript" src="../tests/error_bars.js"></script>
+  <script type="text/javascript" src="../tests/fill_step_plot.js"></script>
   <script type="text/javascript" src="../tests/formats.js"></script>
   <script type="text/javascript" src="../tests/grid_per_axis.js"></script>
   <script type="text/javascript" src="../tests/interaction_model.js"></script>
   <script type="text/javascript" src="../tests/simple_drawing.js"></script>
   <script type="text/javascript" src="../tests/step_plot_per_series.js"></script>
   <script type="text/javascript" src="../tests/stacked.js"></script>
-  <!--
-  <script type="text/javascript" src="../tests/tickers.js"></script>
-  -->
   <script type="text/javascript" src="../tests/to_dom_coords.js"></script>
   <script type="text/javascript" src="../tests/resize.js"></script>
   <script type="text/javascript" src="../tests/plugins_legend.js"></script>
   <script type="text/javascript" src="../tests/two_digit_years.js"></script>
+  <script type="text/javascript" src="../tests/hidpi.js"></script>
+  <script type="text/javascript" src="../tests/smooth_plotter.js"></script>
+  <script type="text/javascript" src="../tests/fast_canvas_proxy.js"></script>
   <script type="text/javascript" src="../tests/update_options.js"></script>
   <script type="text/javascript" src="../tests/update_while_panning.js"></script>
   <script type="text/javascript" src="../tests/utils_test.js"></script>
index 05927a5..4b1ea40 100644 (file)
@@ -24,12 +24,12 @@ var DygraphsLocalTester = function() {
  * In some cases we will still allow warnings to be warnings, however.
  */
 DygraphsLocalTester.prototype.overrideWarn = function() {
-  // save Dygraph.warn so we can catch warnings.
-  var originalDygraphWarn = Dygraph.warn;
-  Dygraph.warn = function(msg) {
-    // This warning is still
+  // save console.warn so we can catch warnings.
+  var originalWarn = console.warn;
+  console.warn = function(msg) {
+    // This warning is pervasive enough that we'll let it slide (for now).
     if (msg == "Using default labels. Set labels explicitly via 'labels' in the options parameter") {
-      originalDygraphWarn(msg);
+      originalWarn(msg);
       return;
     }
     throw 'Warnings not permitted: ' + msg;
index af524a0..5fe1739 100644 (file)
@@ -34,7 +34,8 @@ CallbackTestCase.prototype.testHighlightCallbackIsCalled = function() {
   var h_row;
   var h_pts;
 
-  var highlightCallback  =  function(e, x, pts, row) {
+  var highlightCallback = function(e, x, pts, row) {
+    assertEquals(g, this);
     h_row = row;
     h_pts = pts;
   };
@@ -64,12 +65,13 @@ CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
   var called = false;
 
   var callback = function() {
+    assertEquals(g, this);
     called = true;
   };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
-      drawPointCallback : callback,
+      drawPointCallback: callback,
     });
 
   assertFalse(called);
@@ -80,18 +82,21 @@ CallbackTestCase.prototype.testDrawPointCallback_disabled = function() {
  */
 CallbackTestCase.prototype.testDrawPointCallback_enabled = function() {
   var called = false;
+  var callbackThis = null;
 
   var callback = function() {
+    callbackThis = this;
     called = true;
   };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
-      drawPoints : true,
-      drawPointCallback : callback
+      drawPoints: true,
+      drawPointCallback: callback
     });
 
   assertTrue(called);
+  assertEquals(g, callbackThis);
 };
 
 /**
@@ -102,23 +107,24 @@ CallbackTestCase.prototype.testDrawPointCallback_pointSize = function() {
   var count = 0;
 
   var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
+    assertEquals(g, this);
     pointSize = pointSizeParam;
     count++;
   };
 
   var graph = document.getElementById("graph");
   var g = new Dygraph(graph, data, {
-      drawPoints : true,
-      drawPointCallback : callback
+      drawPoints: true,
+      drawPointCallback: callback
     });
 
   assertEquals(1.5, pointSize);
   assertEquals(12, count); // one call per data point.
 
   var g = new Dygraph(graph, data, {
-      drawPoints : true,
-      drawPointCallback : callback,
-      pointSize : 8
+      drawPoints: true,
+      drawPointCallback: callback,
+      pointSize: 8
     });
 
   assertEquals(8, pointSize);
@@ -133,6 +139,7 @@ CallbackTestCase.prototype.testDrawPointCallback_isolated = function() {
 
   var g;
   var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam) {
+    assertEquals(g, this);
     var dx = g.toDataXCoord(cx);
     xvalues.push(dx);
     Dygraph.Circles.DEFAULT.apply(this, arguments);
@@ -174,7 +181,8 @@ CallbackTestCase.prototype.testDrawPointCallback_isolated = function() {
 CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
   var called = false;
 
-  var drawHighlightPointCallback  = function() {
+  var drawHighlightPointCallback = function() {
+    assertEquals(g, this);
     called = true;
   };
 
@@ -182,8 +190,8 @@ CallbackTestCase.prototype.testDrawHighlightPointCallbackIsCalled = function() {
   var g = new Dygraph(graph, data,
       {
         width: 100,
-        height : 100,
-        drawHighlightPointCallback : drawHighlightPointCallback
+        height: 100,
+        drawHighlightPointCallback: drawHighlightPointCallback
       });
 
   assertFalse(called);
@@ -219,7 +227,8 @@ var runClosestTest = function(isStacked, widthNormal, widthHighlighted) {
         }
       });
 
-  var highlightCallback  =  function(e, x, pts, row, set) {
+  var highlightCallback = function(e, x, pts, row, set) {
+    assertEquals(g, this);
     h_row = row;
     h_pts = pts;
     h_series = set;
@@ -285,7 +294,7 @@ CallbackTestCase.prototype.testClosestPointStackedCallback = function() {
  */
 CallbackTestCase.prototype.testClosestPointCallbackCss1 = function() {
   var css = "div.dygraph-legend > span { display: block; }\n" +
-    "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n";
+      "div.dygraph-legend > span.highlight { border: 1px solid grey; }\n";
   this.styleSheet.innerHTML = css;
   runClosestTest(false, 2, 4);
   this.styleSheet.innerHTML = '';
@@ -296,7 +305,7 @@ CallbackTestCase.prototype.testClosestPointCallbackCss1 = function() {
  */
 CallbackTestCase.prototype.testClosestPointCallbackCss2 = function() {
   var css = "div.dygraph-legend > span { display: none; }\n" +
-    "div.dygraph-legend > span.highlight { display: inline; }\n";
+      "div.dygraph-legend > span.highlight { display: inline; }\n";
   this.styleSheet.innerHTML = css;
   runClosestTest(false, 10, 15);
   this.styleSheet.innerHTML = '';
@@ -342,7 +351,8 @@ CallbackTestCase.prototype.testNaNData = function() {
   var h_row;
   var h_pts;
 
-  var highlightCallback  =  function(e, x, pts, row) {
+  var highlightCallback = function(e, x, pts, row) {
+    assertEquals(g, this);
     h_row = row;
     h_pts = pts;
   };
@@ -394,7 +404,8 @@ CallbackTestCase.prototype.testNaNDataStack = function() {
   var h_row;
   var h_pts;
 
-  var highlightCallback  =  function(e, x, pts, row) {
+  var highlightCallback = function(e, x, pts, row) {
+    assertEquals(g, this);
     h_row = row;
     h_pts = pts;
   };
@@ -459,7 +470,8 @@ CallbackTestCase.prototype.testGapHighlight = function() {
   var h_row;
   var h_pts;
 
-  var highlightCallback  =  function(e, x, pts, row) {
+  var highlightCallback = function(e, x, pts, row) {
+    assertEquals(g, this);
     h_row = row;
     h_pts = pts;
   };
@@ -472,7 +484,7 @@ CallbackTestCase.prototype.testGapHighlight = function() {
      connectSeparatedPoints: true,
      drawPoints: true,
      labels: ['x', 'A', 'B'],
-     highlightCallback : highlightCallback
+     highlightCallback: highlightCallback
   });
 
   DygraphOps.dispatchMouseMove(g, 1.1, 10);
@@ -533,6 +545,7 @@ CallbackTestCase.prototype.testFailedResponse = function() {
 CallbackTestCase.prototype.testHighlightCallbackRow = function() {
   var highlightRow;
   var highlightCallback = function(e, x, pts, row) {
+    assertEquals(g, this);
     highlightRow = row;
   };
 
@@ -581,6 +594,7 @@ CallbackTestCase.prototype.underlayCallback_noSeries = function() {
   var yMin, yMax;
 
   var callback = function(canvas, area, g) {
+    assertEquals(g, this);
     called = true;
     yMin = g.yAxisRange(0)[0];
     yMax = g.yAxisRange(0)[1];
@@ -604,6 +618,7 @@ CallbackTestCase.prototype.underlayCallback_yAxisRange = function() {
   var yMin, yMax;
 
   var callback = function(canvas, area, g) {
+    assertEquals(g, this);
     yMin = g.yAxisRange(0)[0];
     yMax = g.yAxisRange(0)[1];
   };
@@ -622,73 +637,75 @@ CallbackTestCase.prototype.underlayCallback_yAxisRange = function() {
  * Test that drawPointCallback is called for isolated points and correct idx for the point is returned.
  */
 CallbackTestCase.prototype.testDrawPointCallback_idx = function() {
-    var indices = [];
-
-    var g;
-    var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
-        indices.push(idx);
-        Dygraph.Circles.DEFAULT.apply(this, arguments);
-    };
-
-    var graph = document.getElementById("graph");
-
-    var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]];
-    var graphOpts = {
-        labels: ['X', 'Y'],
-        valueRange: [0, 4],
-        drawPoints : false,
-        drawPointCallback : callback,
-        pointSize : 8
-    };
-
-    // Test that correct idx for isolated points are passed to the callback.
-    g = new Dygraph(graph, testdata, graphOpts);
-    assertEquals(2, indices.length);
-    assertEquals([3, 5],indices);
-
-    // Test that correct indices for isolated points + gap points are passed to the callback when
-    // drawGapEdgePoints is set.  This should add one point at the right
-    // edge of the segment at x=11, but not at the graph edge at x=10.
-    indices = []; // Reset for new test
-    graphOpts.drawGapEdgePoints = true;
-    g = new Dygraph(graph, testdata, graphOpts);
-    assertEquals(3, indices.length);
-    assertEquals([1, 3, 5],indices);
-
-
-    //Test that correct indices are passed to the callback when zoomed in.
-    indices = []; // Reset for new test
-    graphOpts.dateWindow = [12.5,13.5]
-    graphOpts.drawPoints = true;
-    testdata = [[10, 2], [11, 3], [12, 4], [13, 2], [14, 5], [15, 3]];
-    g = new Dygraph(graph, testdata, graphOpts);
-    assertEquals(3, indices.length);
-    assertEquals([2, 3, 4],indices);
+  var indices = [];
+
+  var g;
+  var callback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
+    assertEquals(g, this);
+    indices.push(idx);
+    Dygraph.Circles.DEFAULT.apply(this, arguments);
+  };
+
+  var graph = document.getElementById("graph");
+
+  var testdata = [[10, 2], [11, 3], [12, NaN], [13, 2], [14, NaN], [15, 3]];
+  var graphOpts = {
+      labels: ['X', 'Y'],
+      valueRange: [0, 4],
+      drawPoints : false,
+      drawPointCallback : callback,
+      pointSize : 8
+  };
+
+  // Test that correct idx for isolated points are passed to the callback.
+  g = new Dygraph(graph, testdata, graphOpts);
+  assertEquals(2, indices.length);
+  assertEquals([3, 5],indices);
+
+  // Test that correct indices for isolated points + gap points are passed to the callback when
+  // drawGapEdgePoints is set.  This should add one point at the right
+  // edge of the segment at x=11, but not at the graph edge at x=10.
+  indices = []; // Reset for new test
+  graphOpts.drawGapEdgePoints = true;
+  g = new Dygraph(graph, testdata, graphOpts);
+  assertEquals(3, indices.length);
+  assertEquals([1, 3, 5],indices);
+
+
+  //Test that correct indices are passed to the callback when zoomed in.
+  indices = []; // Reset for new test
+  graphOpts.dateWindow = [12.5,13.5]
+  graphOpts.drawPoints = true;
+  testdata = [[10, 2], [11, 3], [12, 4], [13, 2], [14, 5], [15, 3]];
+  g = new Dygraph(graph, testdata, graphOpts);
+  assertEquals(3, indices.length);
+  assertEquals([2, 3, 4],indices);
 };
 
 /**
  * Test that the correct idx is returned for the point in the onHiglightCallback.
   */
 CallbackTestCase.prototype.testDrawHighlightPointCallback_idx = function() {
-    var idxToCheck = null;
-
-    var drawHighlightPointCallback  = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
-        idxToCheck = idx;
-    };
-    var testdata = [[1, 2], [2, 3], [3, NaN], [4, 2], [5, NaN], [6, 3]];
-    var graph = document.getElementById("graph");
-    var g = new Dygraph(graph, testdata,
-        {
-            drawHighlightPointCallback : drawHighlightPointCallback
-        });
-
-    assertNull(idxToCheck);
-    DygraphOps.dispatchMouseMove(g, 3, 0);
-    // check that NaN point is not highlighted
-    assertNull(idxToCheck);
-    DygraphOps.dispatchMouseMove(g, 1, 2);
-    // check that correct index is returned
-    assertEquals(0,idxToCheck);
-    DygraphOps.dispatchMouseMove(g, 6, 3);
-    assertEquals(5,idxToCheck);
+  var idxToCheck = null;
+
+  var drawHighlightPointCallback = function(g, seriesName, canvasContext, cx, cy, color, pointSizeParam,idx) {
+    assertEquals(g, this);
+    idxToCheck = idx;
+  };
+  var testdata = [[1, 2], [2, 3], [3, NaN], [4, 2], [5, NaN], [6, 3]];
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, testdata,
+      {
+          drawHighlightPointCallback : drawHighlightPointCallback
+      });
+
+  assertNull(idxToCheck);
+  DygraphOps.dispatchMouseMove(g, 3, 0);
+  // check that NaN point is not highlighted
+  assertNull(idxToCheck);
+  DygraphOps.dispatchMouseMove(g, 1, 2);
+  // check that correct index is returned
+  assertEquals(0,idxToCheck);
+  DygraphOps.dispatchMouseMove(g, 6, 3);
+  assertEquals(5,idxToCheck);
 };
diff --git a/auto_tests/tests/date_ticker.js b/auto_tests/tests/date_ticker.js
new file mode 100644 (file)
index 0000000..be33b35
--- /dev/null
@@ -0,0 +1,249 @@
+/**
+ * @fileoverview Test cases for the tick-generating functions.
+ * These were generated by adding logging code to the old ticker functions. The
+ * tests serve to track existing behavior should it change in the future.
+ *
+ * @author danvdk@gmail.com (Dan Vanderkam)
+ */
+
+var DateTickerTestCase = TestCase("date-ticker-tests");
+
+DateTickerTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+DateTickerTestCase.prototype.createOptionsViewForAxis = function(axis, dict) {
+  return function (x) {
+    if (dict && dict.hasOwnProperty(x)) {
+      return dict[x];
+    }
+    if (Dygraph.DEFAULT_ATTRS.axes[axis].hasOwnProperty(x)) {
+      return Dygraph.DEFAULT_ATTRS.axes[axis][x];
+    }
+    if (Dygraph.DEFAULT_ATTRS.hasOwnProperty(x)) {
+      return Dygraph.DEFAULT_ATTRS[x];
+    }
+    if (x == 'axisLabelFormatter') return null;
+    throw "mysterious " + axis + "-axis option: " + x;
+  };
+};
+
+DateTickerTestCase.prototype.testBasicDateTicker = function() {
+  var opts = {labelsUTC: true};
+  var options = this.createOptionsViewForAxis('x', opts);
+  
+  var ticks = Dygraph.dateTicker(-1797534000000, 1255579200000, 800, options);
+  var expected_ticks = [
+      {"v":-1577923200000,"label":"1920"},
+      {"v":-1262304000000,"label":"1930"},
+      {"v":-946771200000, "label":"1940"},
+      {"v":-631152000000, "label":"1950"},
+      {"v":-315619200000, "label":"1960"},
+      {"v": 0,            "label":"1970"},
+      {"v": 315532800000, "label":"1980"},
+      {"v": 631152000000, "label":"1990"},
+      {"v": 946684800000, "label":"2000"}
+  ];
+  assertEquals(expected_ticks, ticks);
+  
+  var start = Date.UTC(1999, 11, 31, 14, 0, 0);
+  var end = Date.UTC(2000,  0,  1, 12, 0, 0);
+  var granularity = Dygraph.TWO_HOURLY;
+  ticks = Dygraph.getDateAxis(start, end, granularity, options);
+  expected_ticks = [ // months of the year are zero-based.
+      {v: Date.UTC(1999, 11, 31, 14, 0, 0), label: '14:00'},
+      {v: Date.UTC(1999, 11, 31, 16, 0, 0), label: '16:00'},
+      {v: Date.UTC(1999, 11, 31, 18, 0, 0), label: '18:00'},
+      {v: Date.UTC(1999, 11, 31, 20, 0, 0), label: '20:00'},
+      {v: Date.UTC(1999, 11, 31, 22, 0, 0), label: '22:00'},
+      {v: Date.UTC(2000,  0,  1,  0, 0, 0), label: '01Jan'},
+      {v: Date.UTC(2000,  0,  1,  2, 0, 0), label: '02:00'},
+      {v: Date.UTC(2000,  0,  1,  4, 0, 0), label: '04:00'},
+      {v: Date.UTC(2000,  0,  1,  6, 0, 0), label: '06:00'},
+      {v: Date.UTC(2000,  0,  1,  8, 0, 0), label: '08:00'},
+      {v: Date.UTC(2000,  0,  1, 10, 0, 0), label: '10:00'},
+      {v: Date.UTC(2000,  0,  1, 12, 0, 0), label: '12:00'}
+  ];
+  assertEquals(expected_ticks, ticks);
+};
+
+DateTickerTestCase.prototype.testAllDateTickers = function() {
+  var opts = {labelsUTC: true};
+  var options = this.createOptionsViewForAxis('x', opts);
+
+  // For granularities finer than MONTHLY, the first tick returned tick 
+  // could lie outside [start_time, end_time] range in the original code.
+  // In these tests, those spurious ticks are removed to test new behavior.
+  
+  assertEquals([{"v":-1577923200000,"label":"1920"},{"v":-1262304000000,"label":"1930"},{"v":-946771200000,"label":"1940"},{"v":-631152000000,"label":"1950"},{"v":-315619200000,"label":"1960"},{"v":0,"label":"1970"},{"v":315532800000,"label":"1980"},{"v":631152000000,"label":"1990"},{"v":946684800000,"label":"2000"}], Dygraph.dateTicker(-1797552000000, 1255561200000, 800, options));
+  assertEquals([{"v":-5364662400000,"label":"1800"},{"v":-2208988800000,"label":"1900"}], Dygraph.dateTicker(-6122044800000, 189302400000, 480, options));
+  assertEquals([{"v":1041120000000,"label":"29Dec"},{"v":1041724800000,"label":"05Jan"},{"v":1042329600000,"label":"12Jan"},{"v":1042934400000,"label":"19Jan"},{"v":1043539200000,"label":"26Jan"},{"v":1044144000000,"label":"02Feb"},{"v":1044748800000,"label":"09Feb"},{"v":1045353600000,"label":"16Feb"}], Dygraph.dateTicker(1041120000000, 1045353600000, 640, options));
+  assertEquals([{"v":1041379200000,"label":"Jan 2003"},{"v":1072915200000,"label":"Jan 2004"},{"v":1104537600000,"label":"Jan 2005"},{"v":1136073600000,"label":"Jan 2006"},{"v":1167609600000,"label":"Jan 2007"},{"v":1199145600000,"label":"Jan 2008"},{"v":1230768000000,"label":"Jan 2009"},{"v":1262304000000,"label":"Jan 2010"},{"v":1293840000000,"label":"Jan 2011"}], Dygraph.dateTicker(1041120000000, 1307833200000, 800, options));
+  assertEquals([{"v":1159660800000,"label":"01Oct"},{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"}], Dygraph.dateTicker(1159657200000, 1162252800000, 480, options));
+  assertEquals([{"v":1159660800000,"label":"01Oct"},{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"}], Dygraph.dateTicker(1159657200000, 1162252800000, 640, options));
+  assertEquals([{"v":1159660800000,"label":"01Oct"},{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"},{"v":1162684800000,"label":"05Nov"},{"v":1163289600000,"label":"12Nov"},{"v":1163894400000,"label":"19Nov"},{"v":1164499200000,"label":"26Nov"}], Dygraph.dateTicker(1159657200000, 1164758400000, 1150, options));
+  assertEquals([{"v":1159660800000,"label":"Oct 2006"},{"v":1162339200000,"label":"Nov 2006"}], Dygraph.dateTicker(1159657200000, 1164758400000, 400, options));
+  assertEquals([{"v":1159660800000,"label":"01Oct"},{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"},{"v":1162684800000,"label":"05Nov"},{"v":1163289600000,"label":"12Nov"},{"v":1163894400000,"label":"19Nov"},{"v":1164499200000,"label":"26Nov"}], Dygraph.dateTicker(1159657200000, 1164758400000, 500, options));
+  assertEquals([{"v":1159660800000,"label":"01Oct"},{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"},{"v":1162684800000,"label":"05Nov"},{"v":1163289600000,"label":"12Nov"},{"v":1163894400000,"label":"19Nov"},{"v":1164499200000,"label":"26Nov"}], Dygraph.dateTicker(1159657200000, 1164758400000, 600, options));
+  assertEquals([{"v":1160265600000,"label":"08Oct"},{"v":1160870400000,"label":"15Oct"},{"v":1161475200000,"label":"22Oct"},{"v":1162080000000,"label":"29Oct"},{"v":1162684800000,"label":"05Nov"},{"v":1163289600000,"label":"12Nov"}], Dygraph.dateTicker(1160243979962, 1163887694248, 600, options));
+
+  assertEquals([{"v":1160870400000,"label":"15Oct"}], Dygraph.dateTicker(1160521200000, 1161298800000, 480, options));
+  assertEquals([{"v":1161475200000,"label":"22Oct"},{"v":1161561600000,"label":"23Oct"},{"v":1161648000000,"label":"24Oct"},{"v":1161734400000,"label":"25Oct"},{"v":1161820800000,"label":"26Oct"},{"v":1161907200000,"label":"27Oct"},{"v":1161993600000,"label":"28Oct"}], Dygraph.dateTicker(1161471164461, 1161994065957, 600, options));
+  assertEquals([{"v":1161561600000,"label":"23Oct"},{"v":1161583200000,"label":"06:00"},{"v":1161604800000,"label":"12:00"},{"v":1161626400000,"label":"18:00"}], Dygraph.dateTicker(1161557878860, 1161642991675, 600, options));
+  assertEquals([{"v":1161756000000,"label":"06:00"},{"v":1161759600000,"label":"07:00"},{"v":1161763200000,"label":"08:00"},{"v":1161766800000,"label":"09:00"},{"v":1161770400000,"label":"10:00"},{"v":1161774000000,"label":"11:00"},{"v":1161777600000,"label":"12:00"}], Dygraph.dateTicker(1161752537840, 1161777663332, 600, options));
+  assertEquals([{"v":1167609600000,"label":"01Jan"},{"v":1167696000000,"label":"02Jan"},{"v":1167782400000,"label":"03Jan"},{"v":1167868800000,"label":"04Jan"},{"v":1167955200000,"label":"05Jan"},{"v":1168041600000,"label":"06Jan"},{"v":1168128000000,"label":"07Jan"},{"v":1168214400000,"label":"08Jan"},{"v":1168300800000,"label":"09Jan"}], Dygraph.dateTicker(1167609600000, 1168300800000, 480, options));
+  assertEquals([{"v":1167609600000,"label":"Jan 2007"}], Dygraph.dateTicker(1167609600000, 1199059200000, 100, options));
+  assertEquals([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], Dygraph.dateTicker(1167609600000, 1199059200000, 300, options));
+  assertEquals([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], Dygraph.dateTicker(1167609600000, 1199059200000, 480, options));
+  assertEquals([{"v":1167609600000,"label":"Jan 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1191196800000,"label":"Oct 2007"}], Dygraph.dateTicker(1167609600000, 1199059200000, 600, options));
+  assertEquals([{"v":1160870400000,"label":"15Oct"}], Dygraph.dateTicker(1160521200000, 1161298800000, 480, options));
+  assertEquals([{"v":1167609600000,"label":"Jan 2007"},{"v":1170288000000,"label":"Feb 2007"},{"v":1172707200000,"label":"Mar 2007"},{"v":1175385600000,"label":"Apr 2007"},{"v":1177977600000,"label":"May 2007"},{"v":1180656000000,"label":"Jun 2007"},{"v":1183248000000,"label":"Jul 2007"},{"v":1185926400000,"label":"Aug 2007"},{"v":1188604800000,"label":"Sep 2007"},{"v":1191196800000,"label":"Oct 2007"},{"v":1193875200000,"label":"Nov 2007"},{"v":1196467200000,"label":"Dec 2007"}], Dygraph.dateTicker(1167609600000, 1199059200000, 800, options));
+
+  assertEquals([{"v":1293840000000,"label":"Jan 2011"},{"v":1296518400000,"label":"Feb 2011"},{"v":1298937600000,"label":"Mar 2011"},{"v":1301616000000,"label":"Apr 2011"},{"v":1304208000000,"label":"May 2011"},{"v":1306886400000,"label":"Jun 2011"},{"v":1309478400000,"label":"Jul 2011"},{"v":1312156800000,"label":"Aug 2011"}], Dygraph.dateTicker(1293753600000, 1312844400000, 727, options));
+  assertEquals([{"v":1201824000000,"label":"01Feb"},{"v":1201910400000,"label":"02Feb"},{"v":1201996800000,"label":"03Feb"},{"v":1202083200000,"label":"04Feb"},{"v":1202169600000,"label":"05Feb"},{"v":1202256000000,"label":"06Feb"}], Dygraph.dateTicker(1201824000000, 1202256000000, 700, options));
+  assertEquals([{"v":1210118400000,"label":"07May"},{"v":1210140000000,"label":"06:00"},{"v":1210161600000,"label":"12:00"},{"v":1210183200000,"label":"18:00"},{"v":1210204800000,"label":"08May"},{"v":1210226400000,"label":"06:00"},{"v":1210248000000,"label":"12:00"},{"v":1210269600000,"label":"18:00"},{"v":1210291200000,"label":"09May"}], Dygraph.dateTicker(1210114800000, 1210291200000, 480, options));
+  assertEquals([{"v":1210118400000,"label":"07May"},{"v":1210204800000,"label":"08May"},{"v":1210291200000,"label":"09May"},{"v":1210377600000,"label":"10May"},{"v":1210464000000,"label":"11May"}], Dygraph.dateTicker(1210114800000, 1210464000000, 480, options));
+  assertEquals([{"v":1210118400000,"label":"07May"},{"v":1210204800000,"label":"08May"},{"v":1210291200000,"label":"09May"},{"v":1210377600000,"label":"10May"},{"v":1210464000000,"label":"11May"},{"v":1210550400000,"label":"12May"}], Dygraph.dateTicker(1210114800000, 1210550400000, 480, options));
+  assertEquals([{"v":1214870400000,"label":"01Jul"},{"v":1214872200000,"label":"00:30"},{"v":1214874000000,"label":"01:00"},{"v":1214875800000,"label":"01:30"}], Dygraph.dateTicker(1214870400000, 1214877599000, 600, options));
+  assertEquals([{"v":1214870400000,"label":"Jul 2008"},{"v":1217548800000,"label":"Aug 2008"},{"v":1220227200000,"label":"Sep 2008"}], Dygraph.dateTicker(1214866800000, 1222747200000, 600, options));
+  assertEquals([{"v":1215820800000,"label":"12Jul"},{"v":1215842400000,"label":"06:00"},{"v":1215864000000,"label":"12:00"},{"v":1215885600000,"label":"18:00"},{"v":1215907200000,"label":"13Jul"},{"v":1215928800000,"label":"06:00"},{"v":1215950400000,"label":"12:00"},{"v":1215972000000,"label":"18:00"}], Dygraph.dateTicker(1215817200000, 1215989940000, 600, options));
+  assertEquals([{"v":1246752000000,"label":"05Jul"},{"v":1247356800000,"label":"12Jul"},{"v":1247961600000,"label":"19Jul"}], Dygraph.dateTicker(1246402800000, 1248217200000, 600, options));
+  assertEquals([{"v":1246752000000,"label":"05Jul"},{"v":1247356800000,"label":"12Jul"},{"v":1247961600000,"label":"19Jul"},{"v":1248566400000,"label":"26Jul"},{"v":1249171200000,"label":"02Aug"}], Dygraph.dateTicker(1246402800000, 1249340400000, 600, options));
+  assertEquals([{"v":1247356800000,"label":"12Jul"},{"v":1247360400000,"label":"01:00"},{"v":1247364000000,"label":"02:00"},{"v":1247367600000,"label":"03:00"},{"v":1247371200000,"label":"04:00"},{"v":1247374800000,"label":"05:00"},{"v":1247378400000,"label":"06:00"}], Dygraph.dateTicker(1247356800000, 1247378400000, 600, options));
+
+  assertEquals([{"v":1247356800000,"label":"12Jul"},{"v":1247360400000,"label":"01:00"},{"v":1247364000000,"label":"02:00"},{"v":1247367600000,"label":"03:00"},{"v":1247371200000,"label":"04:00"},{"v":1247374800000,"label":"05:00"},{"v":1247378400000,"label":"06:00"}], Dygraph.dateTicker(1247356800000, 1247378400000, 600, options));
+  assertEquals([{"v":1254268800000,"label":"30Sep"},{"v":1254355200000,"label":"01Oct"},{"v":1254441600000,"label":"02Oct"},{"v":1254528000000,"label":"03Oct"},{"v":1254614400000,"label":"04Oct"},{"v":1254700800000,"label":"05Oct"},{"v":1254787200000,"label":"06Oct"},{"v":1254873600000,"label":"07Oct"},{"v":1254960000000,"label":"08Oct"},{"v":1255046400000,"label":"09Oct"},{"v":1255132800000,"label":"10Oct"}], Dygraph.dateTicker(1254222000000, 1255172400000, 900, options));
+  assertEquals([{"v":1254441600000,"label":"02Oct"},{"v":1254528000000,"label":"03Oct"},{"v":1254614400000,"label":"04Oct"},{"v":1254700800000,"label":"05Oct"},{"v":1254787200000,"label":"06Oct"},{"v":1254873600000,"label":"07Oct"},{"v":1254960000000,"label":"08Oct"}], Dygraph.dateTicker(1254394800000, 1254999600000, 900, options));
+  assertEquals([{"v":1259625600000,"label":"01Dec"},{"v":1259712000000,"label":"02Dec"},{"v":1259798400000,"label":"03Dec"},{"v":1259884800000,"label":"04Dec"},{"v":1259971200000,"label":"05Dec"},{"v":1260057600000,"label":"06Dec"},{"v":1260144000000,"label":"07Dec"}], Dygraph.dateTicker(1259625600000, 1260144000000, 480, options));
+  assertEquals([{"v":1259625600000,"label":"01Dec"},{"v":1259712000000,"label":"02Dec"},{"v":1259798400000,"label":"03Dec"},{"v":1259884800000,"label":"04Dec"},{"v":1259971200000,"label":"05Dec"},{"v":1260057600000,"label":"06Dec"},{"v":1260144000000,"label":"07Dec"}], Dygraph.dateTicker(1259625600000, 1260144000000, 600, options));
+  assertEquals([{"v":1260057600000,"label":"06Dec"},{"v":1260662400000,"label":"13Dec"},{"v":1261267200000,"label":"20Dec"},{"v":1261872000000,"label":"27Dec"},{"v":1262476800000,"label":"03Jan"},{"v":1263081600000,"label":"10Jan"},{"v":1263686400000,"label":"17Jan"},{"v":1264291200000,"label":"24Jan"}], Dygraph.dateTicker(1260057600000, 1264291200000, 640, options));
+  assertEquals([{"v":1262304000000,"label":"Jan 2010"},{"v":1264982400000,"label":"Feb 2010"},{"v":1267401600000,"label":"Mar 2010"},{"v":1270080000000,"label":"Apr 2010"}], Dygraph.dateTicker(1262304000000, 1270857600000, 640, options));
+  assertEquals([{"v":1288915200000,"label":"05Nov"},{"v":1288936800000,"label":"06:00"},{"v":1288958400000,"label":"12:00"},{"v":1288980000000,"label":"18:00"},{"v":1289001600000,"label":"06Nov"},{"v":1289023200000,"label":"06:00"},{"v":1289044800000,"label":"12:00"},{"v":1289066400000,"label":"18:00"},{"v":1289088000000,"label":"07Nov"},{"v":1289109600000,"label":"06:00"},{"v":1289131200000,"label":"12:00"},{"v":1289152800000,"label":"18:00"},{"v":1289174400000,"label":"08Nov"},{"v":1289196000000,"label":"06:00"},{"v":1289217600000,"label":"12:00"},{"v":1289239200000,"label":"18:00"},{"v":1289260800000,"label":"09Nov"}], Dygraph.dateTicker(1288911600000, 1289260800000, 1024, options));
+  assertEquals([{"v":1291161600000,"label":"01Dec"},{"v":1291248000000,"label":"02Dec"},{"v":1291334400000,"label":"03Dec"},{"v":1291420800000,"label":"04Dec"},{"v":1291507200000,"label":"05Dec"},{"v":1291593600000,"label":"06Dec"},{"v":1291680000000,"label":"07Dec"},{"v":1291766400000,"label":"08Dec"},{"v":1291852800000,"label":"09Dec"}], Dygraph.dateTicker(1291161600000, 1291852800000, 600, options));
+  assertEquals([{"v":1294358400000,"label":"07Jan"},{"v":1294444800000,"label":"08Jan"},{"v":1294531200000,"label":"09Jan"},{"v":1294617600000,"label":"10Jan"},{"v":1294704000000,"label":"11Jan"},{"v":1294790400000,"label":"12Jan"},{"v":1294876800000,"label":"13Jan"},{"v":1294963200000,"label":"14Jan"}], Dygraph.dateTicker(1294358400000, 1294963200000, 480, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], Dygraph.dateTicker(1307908000112, 1307908050165, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], Dygraph.dateTicker(1307908000112, 1307908051166, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], Dygraph.dateTicker(1307908000112, 1307908052167, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], Dygraph.dateTicker(1307908000112, 1307908053167, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"}], Dygraph.dateTicker(1307908000112, 1307908054168, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], Dygraph.dateTicker(1307908000112, 1307908055169, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], Dygraph.dateTicker(1307908000112, 1307908056169, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], Dygraph.dateTicker(1307908000112, 1307908057170, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], Dygraph.dateTicker(1307908000112, 1307908058171, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"}], Dygraph.dateTicker(1307908000112, 1307908059172, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908060172, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908061174, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908062176, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908063177, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908064178, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], Dygraph.dateTicker(1307908000112, 1307908065178, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], Dygraph.dateTicker(1307908000112, 1307908066178, 800, options));
+  assertEquals([{"v":1307908005000,"label":"19:46:45"},{"v":1307908010000,"label":"19:46:50"},{"v":1307908015000,"label":"19:46:55"},{"v":1307908020000,"label":"19:47"},{"v":1307908025000,"label":"19:47:05"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908035000,"label":"19:47:15"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908045000,"label":"19:47:25"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908055000,"label":"19:47:35"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908065000,"label":"19:47:45"}], Dygraph.dateTicker(1307908000112, 1307908067179, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908068179, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"}], Dygraph.dateTicker(1307908000112, 1307908069179, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908070180, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908071180, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908072181, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908073181, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908074182, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908075182, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908076183, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908077183, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908078184, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"}], Dygraph.dateTicker(1307908000112, 1307908079185, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908080186, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908081187, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908082188, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908083188, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908084189, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908085190, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908086191, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908087192, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908088192, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"}], Dygraph.dateTicker(1307908000112, 1307908089193, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908090194, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908091194, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908092196, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908093196, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908094197, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908095197, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908096198, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908097199, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908098200, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"}], Dygraph.dateTicker(1307908000112, 1307908099200, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908100201, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908101201, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908102202, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908103203, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908104204, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908105205, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908106205, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908107206, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908108209, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"}], Dygraph.dateTicker(1307908000112, 1307908109209, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908110209, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908111210, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908112211, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908113211, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908114212, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908115213, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908116214, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908117214, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908118215, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908119215, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908120217, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908121218, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908122219, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908123219, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908124220, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908125221, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908126222, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908127222, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908128223, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"}], Dygraph.dateTicker(1307908000112, 1307908129223, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], Dygraph.dateTicker(1307908000112, 1307908130224, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], Dygraph.dateTicker(1307908000112, 1307908131225, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], Dygraph.dateTicker(1307908000112, 1307908132226, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], Dygraph.dateTicker(1307908000112, 1307908133227, 800, options));
+  assertEquals([{"v":1307908010000,"label":"19:46:50"},{"v":1307908020000,"label":"19:47"},{"v":1307908030000,"label":"19:47:10"},{"v":1307908040000,"label":"19:47:20"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908060000,"label":"19:47:40"},{"v":1307908070000,"label":"19:47:50"},{"v":1307908080000,"label":"19:48"},{"v":1307908090000,"label":"19:48:10"},{"v":1307908100000,"label":"19:48:20"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908120000,"label":"19:48:40"},{"v":1307908130000,"label":"19:48:50"}], Dygraph.dateTicker(1307908000112, 1307908134227, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908135227, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908136228, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908137230, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908138231, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"}], Dygraph.dateTicker(1307908000112, 1307908139232, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908140233, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908141233, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908142234, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908143240, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908144240, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908145240, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908146241, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908147241, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908148242, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908149243, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908150243, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908151244, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908152245, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908153245, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908154246, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908155247, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908156247, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908157248, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908158249, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908159250, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908160251, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908161252, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908162252, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908163253, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908164254, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908165254, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908166255, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908167256, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908168256, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"}], Dygraph.dateTicker(1307908000112, 1307908169257, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], Dygraph.dateTicker(1307908000112, 1307908170258, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], Dygraph.dateTicker(1307908000112, 1307908171258, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], Dygraph.dateTicker(1307908000112, 1307908172259, 800, options));
+  assertEquals([{"v":1307908020000,"label":"19:47"},{"v":1307908050000,"label":"19:47:30"},{"v":1307908080000,"label":"19:48"},{"v":1307908110000,"label":"19:48:30"},{"v":1307908140000,"label":"19:49"},{"v":1307908170000,"label":"19:49:30"}], Dygraph.dateTicker(1307908000112, 1307908173260, 800, options));
+  assertEquals([{"v":978307200000,"label":"Jan 2001"},{"v":986083200000,"label":"Apr 2001"},{"v":993945600000,"label":"Jul 2001"},{"v":1001894400000,"label":"Oct 2001"}], Dygraph.dateTicker(978307200000, 1001894400000, 400, options));
+};
index 691b24a..7222a21 100644 (file)
@@ -30,3 +30,89 @@ DygraphOptionsTestCase.prototype.testGetSeriesNames = function() {
   var o = new DygraphOptions(g);
   assertEquals(["Y", "Y2", "Y3"], o.seriesNames()); 
 };
+
+/*
+ * Ensures that even if logscale is set globally, it doesn't impact the
+ * x axis.
+ */
+DygraphOptionsTestCase.prototype.testGetLogscaleForX = function() {
+  var opts = {
+    width: 480,
+    height: 320
+  };
+  var data = "X,Y,Y2,Y3\n" +
+      "1,-1,2,3";
+
+  // Kind of annoying that you need a DOM to test the object.
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+  assertFalse(!!g.getOptionForAxis('logscale', 'y'));
+
+  g.updateOptions({ logscale : true });
+  assertFalse(!!g.getOptionForAxis('logscale', 'x'));
+  assertTrue(!!g.getOptionForAxis('logscale', 'y'));
+};
+
+// Helper to gather all warnings emitted by Dygraph constructor.
+// Removes everything after the first open parenthesis in each warning.
+// Returns them in a (possibly empty) list.
+var getWarnings = function(div, data, opts) {
+  var warnings = [];
+  var oldWarn = console.warn;
+  console.warn = function(message) {
+    warnings.push(message.replace(/ \(.*/, ''));
+  };
+  try {
+    new Dygraph(graph, data, opts);
+  } catch (e) {
+  }
+  console.warn = oldWarn;
+  return warnings;
+};
+
+DygraphOptionsTestCase.prototype.testLogWarningForNonexistentOption = function() {
+  if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+    return;  // this test won't pass in non-debug mode.
+  }
+
+  var graph = document.getElementById("graph");
+  var data = "X,Y,Y2,Y3\n" +
+      "1,-1,2,3";
+
+  var expectWarning = function(opts, badOptionName) {
+    DygraphOptions.resetWarnings_();
+    var warnings = getWarnings(graph, data, opts);
+    assertEquals(['Unknown option ' + badOptionName], warnings);
+  };
+  var expectNoWarning = function(opts) {
+    DygraphOptions.resetWarnings_();
+    var warnings = getWarnings(graph, data, opts);
+    assertEquals([], warnings);
+  };
+
+  expectNoWarning({});
+  expectWarning({nonExistentOption: true}, 'nonExistentOption');
+  expectWarning({series: {Y: {nonExistentOption: true}}}, 'nonExistentOption');
+  // expectWarning({Y: {nonExistentOption: true}});
+  expectWarning({axes: {y: {anotherNonExistentOption: true}}}, 'anotherNonExistentOption');
+  expectWarning({highlightSeriesOpts: {anotherNonExistentOption: true}}, 'anotherNonExistentOption');
+  expectNoWarning({highlightSeriesOpts: {strokeWidth: 20}});
+  expectNoWarning({strokeWidth: 20});
+};
+
+DygraphOptionsTestCase.prototype.testOnlyLogsEachWarningOnce = function() {
+  if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+    return;  // this test won't pass in non-debug mode.
+  }
+
+  var graph = document.getElementById("graph");
+  var data = "X,Y,Y2,Y3\n" +
+      "1,-1,2,3";
+
+  var warnings1 = getWarnings(graph, data, {nonExistent: true});
+  var warnings2 = getWarnings(graph, data, {nonExistent: true});
+  assertEquals(['Unknown option nonExistent'], warnings1);
+  assertEquals([], warnings2);
+};
diff --git a/auto_tests/tests/fast_canvas_proxy.js b/auto_tests/tests/fast_canvas_proxy.js
new file mode 100644 (file)
index 0000000..1e4bfaf
--- /dev/null
@@ -0,0 +1,99 @@
+/**
+ * @fileoverview Tests for fastCanvasProxy, which drops superfluous segments.
+ *
+ * @author danvdk@gmail.com (Dan Vanderkam)
+ */
+var fastCanvasProxyTestCase = TestCase("fast-canvas-proxy");
+
+fastCanvasProxyTestCase.prototype.setUp = function() {
+};
+
+fastCanvasProxyTestCase.prototype.tearDown = function() {
+};
+
+var fakeCanvasContext = {
+  moveTo: function() {},
+  lineTo: function() {},
+  beginPath: function() {},
+  closePath: function() {},
+  fill: function() {},
+  stroke: function() {}
+}
+
+function extractMoveToAndLineToCalls(proxy) {
+  var calls = proxy.calls__;
+  var out = [];
+  for (var i = 0; i < calls.length; i++) {
+    var c = calls[i];
+    if (c.name == 'moveTo' || c.name == 'lineTo') {
+      out.push([c.name, c.args[0], c.args[1]]);
+    }
+  }
+  return out;
+}
+
+fastCanvasProxyTestCase.prototype.testExtraMoveTosElided = function() {
+  var htx = new Proxy(fakeCanvasContext);
+  var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx);
+
+  fastProxy.moveTo(1, 1);
+  fastProxy.lineTo(2, 1);
+  fastProxy.moveTo(2, 1);
+  fastProxy.lineTo(3, 1);
+  fastProxy.moveTo(3, 1);
+  fastProxy.stroke();
+
+  assertEquals([['moveTo', 1, 1],
+                ['lineTo', 2, 1],
+                ['lineTo', 3, 1]], extractMoveToAndLineToCalls(htx));
+};
+
+fastCanvasProxyTestCase.prototype.testConsecutiveMoveTosElided = function() {
+  var htx = new Proxy(fakeCanvasContext);
+  var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx);
+
+  fastProxy.moveTo(1, 1);
+  fastProxy.lineTo(2, 1);
+  fastProxy.moveTo(3, 1);
+  fastProxy.moveTo(3.1, 2);
+  fastProxy.moveTo(3.2, 3);
+  fastProxy.stroke();
+
+  assertEquals([['moveTo', 1, 1],
+                ['lineTo', 2, 1],
+                ['moveTo', 3.2, 3]], extractMoveToAndLineToCalls(htx));
+};
+
+fastCanvasProxyTestCase.prototype.testSuperfluousSegmentsElided = function() {
+  var htx = new Proxy(fakeCanvasContext);
+  var fastProxy = DygraphCanvasRenderer._fastCanvasProxy(htx);
+
+  fastProxy.moveTo(0.6, 1);
+  fastProxy.lineTo(0.7, 2);
+  fastProxy.lineTo(0.8, 3);
+  fastProxy.lineTo(0.9, 4);
+  fastProxy.lineTo(1.0, 5);  // max for Math.round(x) == 1
+  fastProxy.lineTo(1.1, 3);
+  fastProxy.lineTo(1.2, 0);  // min for Math.round(x) == 1
+  fastProxy.lineTo(1.3, 1);
+  fastProxy.lineTo(1.4, 2);
+  fastProxy.moveTo(1.4, 2);
+  fastProxy.lineTo(1.5, 2);  // rounding up to 2
+  fastProxy.moveTo(1.5, 2);
+  fastProxy.lineTo(1.6, 3);
+  fastProxy.moveTo(1.6, 3);
+  fastProxy.lineTo(1.7, 30);  // max for Math.round(x) == 2
+  fastProxy.moveTo(1.7, 30);
+  fastProxy.lineTo(1.8, -30);  // min for Math.round(x) == 2
+  fastProxy.moveTo(1.8, -30);
+  fastProxy.lineTo(1.9, 0);
+  fastProxy.moveTo(3, 0);  // dodge the "don't touch the last pixel" rule.
+  fastProxy.stroke();
+
+  assertEquals([['moveTo', 0.6, 1],
+                ['lineTo', 1.0, 5],
+                ['lineTo', 1.2, 0],
+                ['lineTo', 1.7, 30],
+                ['lineTo', 1.8, -30],
+                ['moveTo', 3, 0]], extractMoveToAndLineToCalls(htx));
+};
diff --git a/auto_tests/tests/fill_step_plot.js b/auto_tests/tests/fill_step_plot.js
new file mode 100644 (file)
index 0000000..f40f8a5
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Test if you give null values to dygraph with stepPlot
+ * and fillGraph options enabled
+ *
+ * @author benoitboivin.pro@gmail.com (Benoit Boivin)
+ */
+var fillStepPlotTestCase = TestCase("fill-step-plot");
+
+fillStepPlotTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+fillStepPlotTestCase.origFunc = Dygraph.getContext;
+
+fillStepPlotTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+  Dygraph.getContext = function(canvas) {
+    return new Proxy(fillStepPlotTestCase.origFunc(canvas));
+  };
+};
+
+fillStepPlotTestCase.prototype.tearDown = function() {
+  Dygraph.getContext = fillStepPlotTestCase.origFunc;
+};
+
+
+fillStepPlotTestCase.prototype.testFillStepPlotNullValues = function() {
+  var opts = {
+    labels: ["x","y"],
+    width: 480,
+    height: 320,
+    fillGraph: true,
+    stepPlot: true
+  };
+  var data = [
+    [1,3],
+    [2,0],
+    [3,8],
+    [4,null],
+    [5,9],
+    [6,8],
+    [7,6],
+    [8,3]
+  ];
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  htx = g.hidden_ctx_;
+  var x1 = data[3][0];
+  var y1 = data[2][1];
+  var x2 = data[3][0];
+  var y2 = 0;
+  var xy1 = g.toDomCoords(x1, y1);
+  var xy2 = g.toDomCoords(x2, y2);
+  
+  // Check if a line is drawn between the previous y and the bottom of the chart
+  CanvasAssertions.assertLineDrawn(htx, xy1, xy2, {});
+};
\ No newline at end of file
diff --git a/auto_tests/tests/hidpi.js b/auto_tests/tests/hidpi.js
new file mode 100644 (file)
index 0000000..c595065
--- /dev/null
@@ -0,0 +1,44 @@
+/**
+ * @fileoverview Tests for window.devicePixelRatio > 1.
+ *
+ * @author danvdk@gmail.com (Dan Vanderkam)
+ */
+var hidpiTestCase = TestCase("hidpi");
+
+var savePixelRatio;
+hidpiTestCase.prototype.setUp = function() {
+  savePixelRatio = window.devicePixelRatio;
+  window.devicePixelRatio = 2;
+
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+hidpiTestCase.prototype.tearDown = function() {
+  window.devicePixelRatio = savePixelRatio;
+};
+
+hidpiTestCase.prototype.testDoesntCreateScrollbars = function() {
+  var sw = document.body.scrollWidth;
+  var cw = document.body.clientWidth;
+
+  var graph = document.getElementById("graph");
+  graph.style.width = "70%";  // more than half.
+  graph.style.height = "200px";
+
+  var opts = {};
+  var data = "X,Y\n" +
+      "0,-1\n" +
+      "1,0\n" +
+      "2,1\n" +
+      "3,0\n"
+  ;
+
+  var g = new Dygraph(graph, data, opts);
+
+  // Adding the graph shouldn't cause the width of the page to change.
+  // (essentially, we're checking that we don't end up with a scrollbar)
+  // See http://stackoverflow.com/a/2146905/388951
+  assertEquals(cw, document.body.clientWidth);
+  assertEquals(sw, document.body.scrollWidth);
+};
+
index 3a27759..a78e0ac 100644 (file)
@@ -18,7 +18,7 @@
 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 // THE SOFTWARE.
 
-/** 
+/**
  * @fileoverview Test cases for drawing lines with missing points.
  *
  * @author konigsberg@google.com (Robert Konigsberg)
@@ -85,7 +85,7 @@ MissingPointsTestCase.prototype.testSeparatedPointsDontDraw_expanded_connected =
   var num_lines = 0;
 
   assertEquals(3, CanvasAssertions.numLinesDrawn(htx, '#0000ff'));
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [[56, 275], [161, 212], [370, 87], [475, 25]],
       { strokeStyle: '#0000ff' });
 };
@@ -114,12 +114,12 @@ MissingPointsTestCase.prototype.testConnectSeparatedPoints = function() {
   var htx = g.hidden_ctx_;
 
   assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff'));
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [[56, 225], [223, 25], [391, 125]],
       { strokeStyle: '#0000ff' });
 
   assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000'));
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [[140, 275], [307, 125], [475, 225]],
       { strokeStyle: '#ff0000' });
 };
@@ -155,12 +155,12 @@ MissingPointsTestCase.prototype.testConnectSeparatedPointsWithNan = function() {
 
   // Blue's lines are consecutive, however.
   assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff'));
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [[56, 244], [149, 181], [242, 118]],
       { strokeStyle: '#0000ff' });
 };
 
-/* These lines contain awesome powa!  
+/* These lines contain awesome powa!
   var lines = CanvasAssertions.getLinesDrawn(htx, {strokeStyle: "#0000ff"});
   for (var idx = 0; idx < lines.length; idx++) {
     var line = lines[idx];
@@ -194,9 +194,9 @@ MissingPointsTestCase.prototype.testErrorBarsWithMissingPoints = function() {
   var p1 = g.toDomCoords(data[1][0], data[1][1][0]);
   var p2 = g.toDomCoords(data[3][0], data[3][1][0]);
   var p3 = g.toDomCoords(data[4][0], data[4][1][0]);
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [p0, p1], { strokeStyle: '#ff0000' });
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [p2, p3], { strokeStyle: '#ff0000' });
 };
 
@@ -221,13 +221,13 @@ MissingPointsTestCase.prototype.testErrorBarsWithMissingPointsConnected = functi
   );
 
   var htx = g.hidden_ctx_;
-  
+
   assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000'));
 
   var p1 = g.toDomCoords(data[1][0], data[1][1][0]);
   var p2 = g.toDomCoords(data[3][0], data[3][1][0]);
   var p3 = g.toDomCoords(data[5][0], data[5][1][0]);
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [p1, p2, p3],
       { strokeStyle: '#ff0000' });
 };
@@ -262,7 +262,7 @@ MissingPointsTestCase.prototype.testCustomBarsWithMissingPoints = function() {
   var p0 = g.toDomCoords(data[0][0], data[0][1][1]);
   var p1 = g.toDomCoords(data[1][0], data[1][1][1]);
   CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' });
-  
+
   p0 = g.toDomCoords(data[3][0], data[3][1][1]);
   p1 = g.toDomCoords(data[4][0], data[4][1][1]);
   CanvasAssertions.assertLineDrawn(htx, p0, p1, { strokeStyle: '#ff0000' });
@@ -297,16 +297,17 @@ MissingPointsTestCase.prototype.testCustomBarsWithMissingPointsConnected = funct
   );
 
   var htx = g.hidden_ctx_;
-  
+
   assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#ff0000'));
 
   var p1 = g.toDomCoords(data[1][0], data[1][1][1]);
   var p2 = g.toDomCoords(data[3][0], data[3][1][1]);
   var p3 = g.toDomCoords(data[5][0], data[5][1][1]);
-  CanvasAssertions.assertConsecutiveLinesDrawn(htx, 
+  CanvasAssertions.assertConsecutiveLinesDrawn(htx,
       [p1, p2, p3],
       { strokeStyle: '#ff0000' });
 };
+
 MissingPointsTestCase.prototype.testLeftBoundaryWithMisingPoints = function() {
   var data = [
               [1, null, 3],
@@ -336,7 +337,6 @@ MissingPointsTestCase.prototype.testLeftBoundaryWithMisingPoints = function() {
   g.setSelection(closestRow);
   assertEquals(1, g.selPoints_.length);
   assertEquals(1, g.selPoints_[0].yval);
-  
 
   g.setSelection(3);
   assertEquals(2, g.selPoints_.length);
@@ -344,3 +344,27 @@ MissingPointsTestCase.prototype.testLeftBoundaryWithMisingPoints = function() {
   assertEquals(2, g.selPoints_[0].yval);
   assertEquals(1, g.selPoints_[1].yval);
 };
+
+// Regression test for issue #411
+MissingPointsTestCase.prototype.testEmptySeries = function() {
+  var graphDiv = document.getElementById("graph");
+  var g = new Dygraph(
+       graphDiv,
+       "Time,Empty Series,Series 1,Series 2\n" +
+       "1381134460,,0,100\n" +
+       "1381134461,,1,99\n" +
+       "1381134462,,2,98\n" +
+       "1381134463,,3,97\n" +
+       "1381134464,,4,96\n" +
+       "1381134465,,5,95\n" +
+       "1381134466,,6,94\n" +
+       "1381134467,,7,93\n" +
+       "1381134468,,8,92\n" +
+       "1381134469,,9,91\n", {
+           visibility: [true, false, true],
+           dateWindow: [1381134465, 1381134467]
+       });
+
+  g.setSelection(6);
+  assertEquals("1381134466: Series 2: 94", Util.getLegend(graphDiv));
+};
index fcda671..737e4b5 100644 (file)
@@ -70,3 +70,120 @@ NumericTickerTestCase.prototype.testBasicNumericTicker = function() {
   ];
   assertEquals(expected_ticks, ticks);
 };
+
+/*
+NumericTickerTestCase.prototype.testAllNumericTickers = function() {
+  assertEquals([{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(-0.4, 4.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-1.5,"label":"-1.5"},{"v":-1,"label":"-1"},{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"}], Dygraph.numericTicks(-1.4157430939856124, 1.4157430939856124, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-10,"label":"-10"},{"v":-8,"label":"-8"},{"v":-6,"label":"-6"},{"v":-4,"label":"-4"},{"v":-2,"label":"-2"},{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"}], Dygraph.numericTicks(-10, 9.98046875, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-200,"label":"-200"},{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(-101.10000000000001, 1100.1, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"}], Dygraph.numericTicks(-11.687459005175139, 42.287459005175144, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-12, 12, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-13.19792086872138, 13.197062407353386, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-220,"label":"-220"},{"v":-200,"label":"-200"},{"v":-180,"label":"-180"},{"v":-160,"label":"-160"},{"v":-140,"label":"-140"},{"v":-120,"label":"-120"}], Dygraph.numericTicks(-220, -100, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(-32.8, 132.8, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-40,"label":"-40"},{"v":-30,"label":"-30"},{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(-34.309, 89.279, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":"2.00e-4"},{"v":-17999999,"label":"3.00e-4"},{"v":0.0004,"label":"4.00e-4"},{"v":0.0005,"label":"5.00e-4"}], Dygraph.numericTicks(0, 0.00055, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":0},{"v":0.0001,"label":0.0001},{"v":0.0002,"label":0.0002},{"v":-17999999,"label":0.0003},{"v":0.0004,"label":0.0004},{"v":0.0005,"label":0.0005}], Dygraph.numericTicks(0, 0.00055, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.1,"label":"0.1"},{"v":0.2,"label":"0.2"},{"v":-17999999,"label":"0.3"},{"v":0.4,"label":"0.4"},{"v":0.5,"label":"0.5"},{"v":-17999999,"label":"0.6"},{"v":-17999999,"label":"0.7"},{"v":0.8,"label":"0.8"},{"v":0.9,"label":"0.9"},{"v":1,"label":"1"},{"v":1.1,"label":"1.1"},{"v":-17999998,"label":"1.2"}], Dygraph.numericTicks(0, 1.2, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 100, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 104.53192180924043, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 109.9856877755916, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(0, 11, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 350, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(0, 1100, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17000000,"label":"1M"},{"v":-16000000,"label":"2M"},{"v":-15000000,"label":"3M"},{"v":-14000000,"label":"4M"},{"v":-13000000,"label":"5M"},{"v":-12000000,"label":"6M"},{"v":-11000000,"label":"7M"},{"v":-10000000,"label":"8M"},{"v":-9000000,"label":"9M"},{"v":-8000000,"label":"10M"}], Dygraph.numericTicks(0, 11000000, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 119, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 130.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 131, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17998000,"label":"2000"},{"v":-17996000,"label":"4000"},{"v":-17994000,"label":"6000"},{"v":-17992000,"label":"8000"},{"v":-17990000,"label":"10000"},{"v":-17988000,"label":"12000"},{"v":-17986000,"label":"14000"},{"v":-17984000,"label":"16000"}], Dygraph.numericTicks(0, 16977.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"}], Dygraph.numericTicks(0, 2, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":-17999999,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":-17999998,"label":"1.2"},{"v":-17999998,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":1.8,"label":"1.8"}], Dygraph.numericTicks(0, 2, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"}], Dygraph.numericTicks(0, 2.2, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17800000,"label":"200K"},{"v":-17600000,"label":"400K"},{"v":-17400000,"label":"600K"},{"v":-17200000,"label":"800K"},{"v":-17000000,"label":"1M"},{"v":-16800000,"label":"1.2M"},{"v":-16600000,"label":"1.4M"},{"v":-16400000,"label":"1.6M"},{"v":-16200000,"label":"1.8M"},{"v":-16000000,"label":"2M"}], Dygraph.numericTicks(0, 2200000, 350, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":0,"label":"0"},{"v":50,"label":"50"},{"v":100,"label":"100"},{"v":150,"label":"150"},{"v":200,"label":"200"}], Dygraph.numericTicks(0, 249, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":500,"label":"500"},{"v":-17999000,"label":"1000"},{"v":1500,"label":"1500"},{"v":-17998000,"label":"2000"},{"v":2500,"label":"2500"}], Dygraph.numericTicks(0, 2747.9970998900817, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":-17999000,"label":"1K"},{"v":1200,"label":"1.2K"},{"v":1400,"label":"1.4K"},{"v":1600,"label":"1.6K"},{"v":1800,"label":"1.8K"},{"v":-17998000,"label":"2K"},{"v":2200,"label":"2.2K"},{"v":2400,"label":"2.4K"},{"v":2600,"label":"2.6K"}], Dygraph.numericTicks(0, 2747.9970998900817, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 32.698942321287205, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17500000,"label":"500000"},{"v":-17000000,"label":"1.00e+6"},{"v":-16500000,"label":"1.50e+6"},{"v":-16000000,"label":"2.00e+6"},{"v":-15500000,"label":"2.50e+6"},{"v":-15000000,"label":"3.00e+6"}], Dygraph.numericTicks(0, 3263100.6418021005, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 33.16213467701236, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(0, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0, 4.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":0,"label":"0"},{"v":8,"label":"8"},{"v":16,"label":"16"},{"v":24,"label":"24"},{"v":32,"label":"32"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":0},{"v":8,"label":8},{"v":16,"label":16},{"v":24,"label":24},{"v":32,"label":32},{"v":40,"label":40}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":999982000000,"label":"1T"},{"v":1999982000000,"label":"2T"},{"v":2999982000000,"label":"3T"},{"v":3999982000000,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":0,"label":"0"},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":0},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17999000,"label":"1000"},{"v":-17998000,"label":"2000"},{"v":-17997000,"label":"3000"},{"v":-17996000,"label":"4000"},{"v":-17995000,"label":"5000"}], Dygraph.numericTicks(0, 5451.6, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"}], Dygraph.numericTicks(0, 550, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"}], Dygraph.numericTicks(0, 64.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"},{"v":600,"label":"600"}], Dygraph.numericTicks(0, 667.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.7, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.9347329768293005, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"}], Dygraph.numericTicks(0, 72.6, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 99, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"}], Dygraph.numericTicks(0, 999, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.000001,"label":"1.00e-6"},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":"1.00e-5"},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":""},{"v":-17999999,"label":""},{"v":0.0004,"label":""},{"v":0.0005,"label":""},{"v":-17999999,"label":""},{"v":0.0007,"label":""},{"v":0.0008,"label":""},{"v":-17999999,"label":""},{"v":0.001,"label":"1.00e-3"},{"v":0.002,"label":""},{"v":0.003,"label":""},{"v":0.004,"label":""},{"v":0.005,"label":""},{"v":0.006,"label":""},{"v":0.007,"label":""},{"v":0.008,"label":""},{"v":-17999999,"label":""},{"v":0.01,"label":"0.01"},{"v":0.02,"label":""},{"v":0.03,"label":""},{"v":0.04,"label":""},{"v":0.05,"label":""},{"v":0.06,"label":""},{"v":0.07,"label":""},{"v":0.08,"label":""},{"v":0.09,"label":""},{"v":0.1,"label":"0.1"},{"v":0.2,"label":""},{"v":-17999999,"label":""},{"v":0.4,"label":""},{"v":0.5,"label":""},{"v":-17999999,"label":""},{"v":-17999999,"label":""},{"v":0.8,"label":""},{"v":0.9,"label":""},{"v":1,"label":"1"},{"v":2,"label":""},{"v":3,"label":""},{"v":4,"label":""},{"v":5,"label":""},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":""},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":""},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(0.000001, 1099.9999999, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(0.6, 5.4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 353, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":-17999998,"label":"1.2"},{"v":-17999998,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":-17999998,"label":"1.8"},{"v":2,"label":"2"},{"v":2.2,"label":"2.2"},{"v":-17999997,"label":"2.4"},{"v":2.6,"label":"2.6"},{"v":-17999997,"label":"2.8"},{"v":-17999997,"label":"3"},{"v":3.2,"label":"3.2"},{"v":-17999996,"label":"3.4"},{"v":3.6,"label":"3.6"},{"v":-17999996,"label":"3.8"},{"v":4,"label":"4"},{"v":4.2,"label":"4.2"},{"v":4.4,"label":"4.4"},{"v":4.6,"label":"4.6"},{"v":-17999995,"label":"4.8"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 743, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6386658954698001, 4.8095173522082, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0.7101014279158788, 4.023726495301334, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(1, 109, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"}], Dygraph.numericTicks(1, 3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1, 5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":1},{"v":1.5,"label":1.5},{"v":2,"label":2},{"v":2.5,"label":2.5},{"v":3,"label":3},{"v":3.5,"label":3.5},{"v":4,"label":4},{"v":4.5,"label":4.5}], Dygraph.numericTicks(1, 5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(1, 6, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"}], Dygraph.numericTicks(1, 7, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":"7"},{"v":8,"label":""},{"v":9,"label":"9"}], Dygraph.numericTicks(1, 9, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(1.2, 10.8, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1.2872947778969237, 4.765317192093838, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(1.5, 7.5, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"}], Dygraph.numericTicks(1.7999999999999998, 28.2, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":10,"label":"10"},{"v":10.1,"label":"10.1"},{"v":10.2,"label":"10.2"},{"v":10.3,"label":"10.3"},{"v":10.4,"label":"10.4"},{"v":10.5,"label":"10.5"},{"v":10.6,"label":"10.6"},{"v":10.7,"label":"10.7"},{"v":10.8,"label":"10.8"},{"v":10.9,"label":"10.9"}], Dygraph.numericTicks(10, 11, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"}], Dygraph.numericTicks(100, 200, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":10000,"label":"10000"},{"v":-17988000,"label":"12000"},{"v":-17986000,"label":"14000"},{"v":-17984000,"label":"16000"},{"v":-17982000,"label":"18000"},{"v":-17980000,"label":"20000"},{"v":-17978000,"label":"22000"},{"v":-17976000,"label":"24000"},{"v":-17974000,"label":"26000"},{"v":-17972000,"label":"28000"},{"v":-17970000,"label":"30000"},{"v":-17968000,"label":"32000"},{"v":-17966000,"label":"34000"},{"v":-17964000,"label":"36000"}], Dygraph.numericTicks(10122.8, 36789.2, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":11000,"label":"11000"},{"v":11200,"label":"11200"},{"v":11400,"label":"11400"},{"v":11600,"label":"11600"},{"v":11800,"label":"11800"},{"v":-17988000,"label":"12000"},{"v":12200,"label":"12200"},{"v":12400,"label":"12400"},{"v":12600,"label":"12600"},{"v":12800,"label":"12800"},{"v":-17987000,"label":"13000"},{"v":13200,"label":"13200"},{"v":13400,"label":"13400"}], Dygraph.numericTicks(11110.5, 13579.5, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":162000,"label":"162000"},{"v":-17836000,"label":"164000"},{"v":-17834000,"label":"166000"},{"v":-17832000,"label":"168000"},{"v":-17830000,"label":"170000"},{"v":-17828000,"label":"172000"},{"v":-17826000,"label":"174000"},{"v":-17824000,"label":"176000"},{"v":-17822000,"label":"178000"}], Dygraph.numericTicks(163038.4, 179137.6, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(2, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(2.6, 7.4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(24, 96, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 20, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20})));
+  assertEquals([{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"},{"v":45,"label":"45"},{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"},{"v":85,"label":"85"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(28.33333333333333, 88.33333333333334, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(3, 5, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":3000,"label":"3K"},{"v":2500,"label":"2.5K"},{"v":-17998000,"label":"2K"},{"v":1500,"label":"1.5K"},{"v":-17999000,"label":"1K"},{"v":500,"label":"500"}], Dygraph.numericTicks(3000, 0, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(33.11333333333334, 83.75333333333333, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(36.921241050119335, 88.32696897374701, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":50,"label":""},{"v":60,"label":"60"},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":"300"},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":"600"},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"},{"v":-17998000,"label":""},{"v":-17997000,"label":"3000"},{"v":-17996000,"label":""},{"v":-17995000,"label":""},{"v":-17994000,"label":"6000"},{"v":-17993000,"label":""},{"v":-17992000,"label":""},{"v":-17991000,"label":""},{"v":-17990000,"label":"10000"}], Dygraph.numericTicks(41.220000000000084, 15576.828000000018, 400, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(44.5, 98.5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":"50"},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":"500"},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":-17999000,"label":"1000"}], Dygraph.numericTicks(5, 1099.5, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"}], Dygraph.numericTicks(52.5, 82.5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":68,"label":"68"},{"v":70,"label":"70"},{"v":72,"label":"72"},{"v":74,"label":"74"},{"v":76,"label":"76"},{"v":78,"label":"78"},{"v":80,"label":"80"}], Dygraph.numericTicks(69, 81, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":0,"label":"0"},{"v":-17980000,"label":"20K"},{"v":-17960000,"label":"40K"},{"v":-17940000,"label":"60K"},{"v":-17920000,"label":"80K"}], Dygraph.numericTicks(7921.099999999999, 81407.9, 240, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":9,"label":"9"},{"v":10,"label":"10"},{"v":11,"label":"11"},{"v":12,"label":"12"},{"v":13,"label":"13"},{"v":14,"label":"14"},{"v":15,"label":"15"},{"v":16,"label":"16"},{"v":17,"label":"17"},{"v":18,"label":"18"}], Dygraph.numericTicks(9.2, 18.8, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"},{"v":200,"label":"200"}], Dygraph.numericTicks(90, 210, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
+  assertEquals([{"v":95,"label":"95"},{"v":96,"label":"96"},{"v":97,"label":"97"},{"v":98,"label":"98"},{"v":99,"label":"99"},{"v":100,"label":"100"},{"v":101,"label":"101"},{"v":102,"label":"102"},{"v":103,"label":"103"},{"v":104,"label":"104"}], Dygraph.numericTicks(95.71121718377088, 104.23150357995226, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+  assertEquals([{"v":950,"label":"950"},{"v":-17999000,"label":"1000"},{"v":1050,"label":"1050"},{"v":1100,"label":"1100"},{"v":1150,"label":"1150"},{"v":1200,"label":"1200"}], Dygraph.numericTicks(980.1, 1218.9, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
+};
+*/
index 7289e0c..8a7e081 100644 (file)
@@ -20,7 +20,9 @@ perSeriesTestCase.prototype.testPerSeriesFill = function() {
     drawYGrid: false,
     drawXAxis: false,
     drawYAxis: false,
-    Y: { fillGraph: true },
+    series: {
+      Y: { fillGraph: true },
+    },
     colors: [ '#FF0000', '#0000FF' ],
     fillAlpha: 0.15
   };
index 8dde4d8..3605113 100644 (file)
@@ -7,6 +7,13 @@ var pluginsTestCase = TestCase("plugins");
 
 pluginsTestCase.prototype.setUp = function() {
   document.body.innerHTML = "<div id='graph'></div>";
+
+  this.data = "X,Y1,Y2\n" +
+      "0,1,2\n" +
+      "1,2,1\n" +
+      "2,1,2\n" +
+      "3,2,1\n"
+  ;
 };
 
 pluginsTestCase.prototype.tearDown = function() {
@@ -32,15 +39,197 @@ pluginsTestCase.prototype.testWillDrawChart = function() {
     return p;
   })();
 
-  var data = "X,Y1,Y2\n" +
-      "0,1,1\n" +
-      "1,1,1\n" +
-      "2,1,1\n" +
-      "3,1,1\n"
-  ;
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, this.data, {plugins: [plugin]});
+
+  assertEquals(1, draw);
+};
+
+pluginsTestCase.prototype.testPassingInstance = function() {
+  // You can also pass an instance of a plugin instead of a Plugin class.
+  var draw = 0;
+  var p = {
+    activate: function(g) {
+      return {
+        willDrawChart: this.willDrawChart
+      }
+    },
+    willDrawChart: function(g) {
+      draw++;
+    }
+  };
 
   var graph = document.getElementById("graph");
-  var g = new Dygraph(graph, data, { plugins : [ plugin ] });
+  var g = new Dygraph(graph, this.data, {plugins: [p]});
 
   assertEquals(1, draw);
 };
+
+pluginsTestCase.prototype.testPreventDefault = function() {
+  var data1 = "X,Y\n" +
+      "20,-1\n" +
+      "21,0\n" +
+      "22,1\n" +
+      "23,0\n";
+
+  var events = [];
+
+  var p = {
+    pointClickPreventDefault: false,
+    clickPreventDefault: false,
+    activate: function(g) {
+      return {
+        pointClick: this.pointClick,
+        click: this.click
+      }
+    },
+    pointClick: function(e) {
+      events.push(['plugin.pointClick', e.point.xval, e.point.yval]);
+      if (this.pointClickPreventDefault) {
+        e.preventDefault();
+      }
+    },
+    click: function(e) {
+      events.push(['plugin.click', e.xval]);
+      if (this.clickPreventDefault) {
+        e.preventDefault();
+      }
+    }
+  };
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data1, {
+    plugins: [p],
+    clickCallback: function(e, x) {
+      events.push(['clickCallback', x]);
+    },
+    pointClickCallback: function(e, pt) {
+      events.push(['pointClickCallback', pt.xval, pt.yval]);
+    }
+  });
+
+  // Click the point at x=20
+  function clickOnPoint() {
+    var x = 58, y = 275;
+    DygraphOps.dispatchMouseDown_Point(g, x, y);
+    DygraphOps.dispatchMouseMove_Point(g, x, y);
+    DygraphOps.dispatchMouseUp_Point(g, x, y);
+  }
+
+  p.pointClickPreventDefault = false;
+  p.clickPreventDefault = false;
+  clickOnPoint();
+  assertEquals([
+    ['plugin.pointClick', 20, -1],
+    ['pointClickCallback', 20, -1],
+    ['plugin.click', 20],
+    ['clickCallback', 20]
+  ], events);
+
+  events = [];
+  p.pointClickPreventDefault = true;
+  p.clickPreventDefault = false;
+  clickOnPoint();
+  assertEquals([
+    ['plugin.pointClick', 20, -1]
+  ], events);
+
+  events = [];
+  p.pointClickPreventDefault = false;
+  p.clickPreventDefault = true;
+  clickOnPoint();
+  assertEquals([
+    ['plugin.pointClick', 20, -1],
+    ['pointClickCallback', 20, -1],
+    ['plugin.click', 20]
+  ], events);
+};
+
+pluginsTestCase.prototype.testEventSequence = function() {
+  var events = [];
+
+  var eventLogger = function(name) {
+    return function(e) {
+      events.push(name);
+    };
+  };
+
+  var p = {
+    activate: function(g) {
+      return {
+        clearChart: eventLogger('clearChart'),
+        predraw: eventLogger('predraw'),
+        willDrawChart: eventLogger('willDrawChart'),
+        didDrawChart: eventLogger('didDrawChart'),
+        dataWillUpdate: eventLogger('dataWillUpdate'),
+        dataDidUpdate: eventLogger('dataDidUpdate')
+      }
+    }
+  };
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, this.data, {plugins: [p]});
+
+  // Initial draw sequence
+  assertEquals([
+   "dataDidUpdate",  // should dataWillUpdate be called here, too?
+   "predraw",
+   "clearChart",
+   "willDrawChart",
+   "didDrawChart"
+  ], events);
+
+  // An options change triggers a redraw, but doesn't change the data.
+  events = [];
+  g.updateOptions({series: {Y1: {color: 'blue'}}});
+  assertEquals([
+   "predraw",
+   "clearChart",
+   "willDrawChart",
+   "didDrawChart"
+  ], events);
+
+  // A pan shouldn't cause a new "predraw"
+  events = [];
+  DygraphOps.dispatchMouseDown_Point(g, 100, 100, {shiftKey: true});
+  DygraphOps.dispatchMouseMove_Point(g, 200, 100, {shiftKey: true});
+  DygraphOps.dispatchMouseUp_Point(g, 200, 100, {shiftKey: true});
+  assertEquals([
+   "clearChart",
+   "willDrawChart",
+   "didDrawChart"
+  ], events);
+
+  // New data triggers the full sequence.
+  events = [];
+  g.updateOptions({file: this.data + '\n4,1,2'});
+  assertEquals([
+   "dataWillUpdate",
+   "dataDidUpdate",
+   "predraw",
+   "clearChart",
+   "willDrawChart",
+   "didDrawChart"
+  ], events);
+};
+
+pluginsTestCase.prototype.testDestroyCalledInOrder = function() {
+  var destructions = [];
+  var makePlugin = function(name) {
+    return {
+      activate: function(g) { return {} },
+      destroy: function() {
+        destructions.push(name);
+      }
+    };
+  };
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, this.data, {
+    plugins: [makePlugin('p'), makePlugin('q')]
+  });
+
+  assertEquals([], destructions);
+  g.destroy();
+  assertEquals(['q', 'p'], destructions);
+};
index 9f83188..c2b829e 100644 (file)
@@ -115,7 +115,6 @@ RangeSelectorTestCase.prototype.testRangeSelectorOptions = function() {
     showRangeSelector: true,
     rangeSelectorHeight: 30,
     rangeSelectorPlotFillColor: 'lightyellow',
-    rangeSelectorPlotStyleColor: 'yellow',
     labels: ['X', 'Y']
   };
   var data = [
@@ -451,6 +450,39 @@ RangeSelectorTestCase.prototype.testCombinedSeries = function() {
   }, combinedSeries);
 };
 
+// Tests selection of a specific series to average for the mini plot.
+RangeSelectorTestCase.prototype.testSelectedCombinedSeries = function() {
+  var opts = {
+    showRangeSelector: true,
+    labels: ['X', 'Y1', 'Y2', 'Y3', 'Y4'],
+    series: {
+      'Y1': { showInRangeSelector: true },
+      'Y3': { showInRangeSelector: true }
+    }
+  };
+  var data = [
+      [0, 5, 8, 13, 21],  // average (first and third) = 9
+      [5, 1, 3, 7, 14],   // average (first and third) = 4
+      [10, 0, 19, 10, 6]  // average (first and third) = 5
+    ];
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  var rangeSelector = g.getPluginInstance_(Dygraph.Plugins.RangeSelector);
+  assertNotNull(rangeSelector);
+
+  var combinedSeries = rangeSelector.computeCombinedSeriesAndLimits_();
+  assertEquals({
+    yMin: 4 - 5 * 0.25,  // 25% padding on combined series range.
+    yMax: 9 + 5 * 0.25,
+    data: [
+      [0, 9],
+      [5, 4],
+      [10, 5]
+    ]
+  }, combinedSeries);
+};
+
 // Tests data computation for the mini plot with a single error bar series.
 RangeSelectorTestCase.prototype.testSingleCombinedSeriesCustomBars = function() {
   var opts = {
index 2e68c1f..ff220f6 100644 (file)
@@ -48,3 +48,43 @@ SelectionTestCase.prototype.testSetGetSelectionDense = function() {
   g.setSelection(3);
   assertEquals(3, g.getSelection());
 };
+
+SelectionTestCase.prototype.testSetGetSelectionMissingPoints = function() {
+  dataHandler = function() {};
+  dataHandler.prototype = new Dygraph.DataHandlers.DefaultHandler();
+  dataHandler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) {
+    var val = null;
+    if (setName == 'A') {
+      val = 1;
+    } else if (setName == 'B') {
+      val = 2;
+    } else if (setName == 'C') {
+      val = 3;
+    }
+    return [{
+      x: NaN,
+      y: NaN,
+      xval: val,
+      yval: val,
+      name: setName,
+      idx: val - 1
+    }];
+  };
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph,
+    "X,A,B,C\n" +
+    "1,1,null,null\n" +
+    "2,null,2,null\n" +
+    "3,null,null,3\n",
+    {
+      dataHandler: dataHandler
+    }
+  );
+
+  g.setSelection(0);
+  assertEquals(0, g.getSelection());
+  g.setSelection(1);
+  assertEquals(1, g.getSelection());
+  g.setSelection(2);
+  assertEquals(2, g.getSelection());
+};
index 27e9a1a..dde41b8 100644 (file)
@@ -96,12 +96,20 @@ SimpleDrawingTestCase.prototype.testDrawWithAxis = function() {
  */
 SimpleDrawingTestCase.prototype.testDrawSimpleDash = function() {
   var opts = {
-      drawXGrid: false,
-      drawYGrid: false,
-      drawXAxis: false,
-      drawYAxis: false,
+    axes: {
+      x: {
+        drawGrid: false,
+        drawAxis: false
+      },
+      y: {
+        drawGrid: false,
+        drawAxis: false
+      }
+    },
+    series: {
       'Y1': {strokePattern: [25, 7, 7, 7]},
-      colors: ['#ff0000']
+    },
+    colors: ['#ff0000']
   };
 
   var graph = document.getElementById("graph");
diff --git a/auto_tests/tests/smooth_plotter.js b/auto_tests/tests/smooth_plotter.js
new file mode 100644 (file)
index 0000000..2268bb3
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Tests for the smooth (bezier curve) plotter.
+ *
+ * @author danvdk@gmail.com (Dan Vanderkam)
+ */
+var smoothPlotterTestCase = TestCase("smooth-plotter");
+
+var getControlPoints = smoothPlotter._getControlPoints;
+
+smoothPlotterTestCase.prototype.setUp = function() {
+};
+
+smoothPlotterTestCase.prototype.tearDown = function() {
+};
+
+smoothPlotterTestCase.prototype.testNoSmoothing = function() {
+  var lastPt = {x: 10, y: 0},
+      pt = {x: 11, y: 1},
+      nextPt = {x: 12, y: 0},
+      alpha = 0;
+
+  assertEquals([11, 1, 11, 1], getControlPoints(lastPt, pt, nextPt, alpha));
+};
+
+smoothPlotterTestCase.prototype.testHalfSmoothing = function() {
+  var lastPt = {x: 10, y: 0},
+      pt = {x: 11, y: 1},
+      nextPt = {x: 12, y: 0},
+      alpha = 0.5;
+
+  assertEquals([10.5, 1, 11.5, 1], getControlPoints(lastPt, pt, nextPt, alpha));
+}
+
+smoothPlotterTestCase.prototype.testExtrema = function() {
+  var lastPt = {x: 10, y: 0},
+      pt = {x: 11, y: 1},
+      nextPt = {x: 12, y: 1},
+      alpha = 0.5;
+
+  assertEquals([10.5, 0.75, 11.5, 1.25],
+               getControlPoints(lastPt, pt, nextPt, alpha, true));
+
+  assertEquals([10.5, 1, 11.5, 1],
+               getControlPoints(lastPt, pt, nextPt, alpha, false));
+}
index 1185f19..87963ef 100644 (file)
@@ -264,3 +264,74 @@ stackedTestCase.prototype.testInterpolationOptions = function() {
     }
   }
 };
+
+stackedTestCase.prototype.testMultiAxisInterpolation = function() {
+  // Setting 2 axes to test that each axis stacks separately 
+  var opts = {
+    colors: ['#ff0000', '#00ff00', '#0000ff'],
+    stackedGraph: true,
+    series: {
+        "Y1": {
+            axis: 'y',
+        },
+        "Y2": {
+            axis: 'y',
+        },
+        "Y3": {
+            axis: 'y2',
+        },
+        "Y4": {
+            axis: 'y2',
+        }
+    }
+  };
+
+  // The last series is all-NaN, it ought to be treated as all zero
+  // for stacking purposes.
+  var N = NaN;
+  var data = [
+    [100, 1, 2, N, N],
+    [101, 1, 2, 2, N],
+    [102, 1, N, N, N],
+    [103, 1, 2, 4, N],
+    [104, N, N, N, N],
+    [105, 1, 2, N, N],
+    [106, 1, 2, 7, N],
+    [107, 1, 2, 8, N],
+    [108, 1, 2, 9, N],
+    [109, 1, N, N, N]];
+
+  var graph = document.getElementById("graph");
+  g = new Dygraph(graph, data, opts);
+
+  var htx = g.hidden_ctx_;
+  var attrs = {};
+
+  // Check that lines are drawn at the expected positions, using
+  // interpolated values for missing data.
+  CanvasAssertions.assertLineDrawn(
+      htx, g.toDomCoords(100, 2), g.toDomCoords(101, 2), {strokeStyle: '#00ff00'});
+  CanvasAssertions.assertLineDrawn(
+      htx, g.toDomCoords(102, 3), g.toDomCoords(103, 3), {strokeStyle: '#ff0000'});
+  CanvasAssertions.assertLineDrawn(
+      htx, g.toDomCoords(107, 2.71), g.toDomCoords(108, 3), {strokeStyle: '#0000ff'});
+  CanvasAssertions.assertLineDrawn(
+      htx, g.toDomCoords(108, 3), g.toDomCoords(109, 3), {strokeStyle: '#ff0000'});
+
+  // Check that the expected number of line segments gets drawn
+  // for each series. Gaps don't get a line.
+  assertEquals(7, CanvasAssertions.numLinesDrawn(htx, '#ff0000'));
+  assertEquals(4, CanvasAssertions.numLinesDrawn(htx, '#00ff00'));
+  assertEquals(2, CanvasAssertions.numLinesDrawn(htx, '#0000ff'));
+
+  // Check that the selection returns the original (non-stacked)
+  // values and skips gaps.
+  g.setSelection(1);
+  assertEquals("101: Y1: 1 Y2: 2 Y3: 2", Util.getLegend());
+
+  g.setSelection(8);
+  assertEquals("108: Y1: 1 Y2: 2 Y3: 9", Util.getLegend());
+
+  g.setSelection(9);
+  assertEquals("109: Y1: 1", Util.getLegend());
+};
index d1bf323..2364561 100644 (file)
@@ -1,6 +1,11 @@
 /**
  * @fileoverview Test cases for the option "stepPlot" especially for the scenario where the option is not set for the whole graph but for single series.
  *
+ * TODO(danvk): delete this test once dpxdt screenshot tests are part of the
+ *     main dygraphs repo. The tests have extremely specific expectations about
+ *     how drawing is performed. It's more realistic to test the resulting
+ *     pixels.
+ *
  * @author julian.eichstaedt@ch.sauter-bc.com (Fr. Sauter AG)
  */
 var StepTestCase = TestCase("step-plot-per-series");
@@ -148,7 +153,7 @@ StepTestCase.prototype.testMixedModeStepAndLineStackedAndFilled = function() {
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x2, y2base);
-    CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
+    // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x1, y1base);
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
@@ -172,7 +177,7 @@ StepTestCase.prototype.testMixedModeStepAndLineStackedAndFilled = function() {
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x2, y2base);
-    CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
+    // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x1, y1base);
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
@@ -201,7 +206,7 @@ StepTestCase.prototype.testMixedModeStepAndLineStackedAndFilled = function() {
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x2, y2base);
-    CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
+    // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x1, y1base);
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
@@ -225,7 +230,7 @@ StepTestCase.prototype.testMixedModeStepAndLineStackedAndFilled = function() {
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x2, y2base);
-    CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
+    // CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
     xy1 = xy2;
     xy2 = g.toDomCoords(x1, y1base);
     CanvasAssertions.assertLineDrawn(htx, xy1, xy2, attrs);
diff --git a/auto_tests/tests/tickers.disabled-js b/auto_tests/tests/tickers.disabled-js
deleted file mode 100644 (file)
index d3e6fdf..0000000
+++ /dev/null
@@ -1,340 +0,0 @@
-/**
- * @fileoverview Test cases for the tick-generating functions.
- * These were generated by adding logging code to the old ticker functions. The
- * tests serve to track existing behavior should it change in the future.
- *
- * @author danvdk@gmail.com (Dan Vanderkam)
- */
-
-var TickerTestCase = TestCase("ticker-tests");
-
-TickerTestCase.prototype.setUp = function() {
-  document.body.innerHTML = "<div id='graph'></div>";
-};
-
-TickerTestCase.prototype.createOptionsViewForAxis = function(axis, dict) {
-  return function (x) {
-    if (dict && dict.hasOwnProperty(x)) {
-      return dict[x];
-    }
-    if (Dygraph.DEFAULT_ATTRS.axes[axis].hasOwnProperty(x)) {
-      return Dygraph.DEFAULT_ATTRS.axes[axis][x];
-    }
-    if (Dygraph.DEFAULT_ATTRS.hasOwnProperty(x)) {
-      return Dygraph.DEFAULT_ATTRS[x];
-    }
-    if (x == 'axisLabelFormatter') return null;
-    throw "mysterious " + axis + "-axis option: " + x;
-  };
-};
-
-// Broken, since it assumes EST.
-TickerTestCase.prototype._testBasicDateTicker = function() {
-  var ticks = Dygraph.dateTicker(-1797534000000, 1255579200000, 800,
-      this.createOptionsViewForAxis('x'));
-
-  var expected_ticks = [{"v":-1577905200000,"label":"1920"},{"v":-1262286000000,"label":"1930"},{"v":-946753200000,"label":"1940"},{"v":-631134000000,"label":"1950"},{"v":-315601200000,"label":"1960"},{"v":18000000,"label":"1970"},{"v":315550800000,"label":"1980"},{"v":631170000000,"label":"1990"},{"v":946702800000,"label":"2000"}];
-
-  assertEquals(expected_ticks, ticks);
-};
-
-TickerTestCase.prototype.testBasicNumericTicker = function() {
-  var opts = {"logscale":null,"labelsKMG2":false,"labelsKMB":false};
-  var ticks = Dygraph.numericTicks(-0.4, 4.4, 320,
-      this.createOptionsViewForAxis('y', opts));
-
-  var expected_ticks = [{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}];
-
-  assertEquals(expected_ticks, ticks);
-};
-
-// Broken, since it assumes EST.
-TickerTestCase.prototype._testAllDateTickers = function() {
-  assertEquals([{"v":-1577905200000,"label":"1920"},{"v":-1262286000000,"label":"1930"},{"v":-946753200000,"label":"1940"},{"v":-631134000000,"label":"1950"},{"v":-315601200000,"label":"1960"},{"v":18000000,"label":"1970"},{"v":315550800000,"label":"1980"},{"v":631170000000,"label":"1990"},{"v":946702800000,"label":"2000"}], Dygraph.dateTicker(-1797534000000, 1255579200000, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":-5364644400000,"label":"1800"},{"v":-2208970800000,"label":"1900"}], Dygraph.dateTicker(-6122026800000, 189320400000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1041138000000,"label":"29Dec"},{"v":1041742800000,"label":"05Jan"},{"v":1042347600000,"label":"12Jan"},{"v":1042952400000,"label":"19Jan"},{"v":1043557200000,"label":"26Jan"},{"v":1044162000000,"label":"02Feb"},{"v":1044766800000,"label":"09Feb"},{"v":1045371600000,"label":"16Feb"}], Dygraph.dateTicker(1041138000000, 1045371600000, 640, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1041397200000,"label":"Jan 03"},{"v":1072933200000,"label":"Jan 04"},{"v":1104555600000,"label":"Jan 05"},{"v":1136091600000,"label":"Jan 06"},{"v":1167627600000,"label":"Jan 07"},{"v":1199163600000,"label":"Jan 08"},{"v":1230786000000,"label":"Jan 09"},{"v":1262322000000,"label":"Jan 10"},{"v":1293858000000,"label":"Jan 11"}], Dygraph.dateTicker(1041138000000, 1307851200000, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200000,"label":"01Oct"},{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"},{"v":1161489600000,"label":"22Oct"},{"v":1162094400000,"label":"29Oct"}], Dygraph.dateTicker(1159675200000, 1162270800000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200000,"label":"01Oct"},{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"},{"v":1161489600000,"label":"22Oct"},{"v":1162094400000,"label":"29Oct"}], Dygraph.dateTicker(1159675200000, 1162270800000, 640, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200000,"label":"01Oct"},{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"},{"v":1161489600000,"label":"22Oct"},{"v":1162094400000,"label":"29Oct"},{"v":1162699200000,"label":"05Nov"},{"v":1163304000000,"label":"12Nov"},{"v":1163908800000,"label":"19Nov"},{"v":1164513600000,"label":"26Nov"}], Dygraph.dateTicker(1159675200000, 1164776400000, 1150, this.createOptionsViewForAxis('x')));
-  // assertEquals([{"v":1159675200000,"label":"Oct 06"},{"v":1162357200000,"label":"Nov 06"},{"v":null,"label":"undefined NaN"}], Dygraph.dateTicker(1159675200000, 1164776400000, 400, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200000,"label":"01Oct"},{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"},{"v":1161489600000,"label":"22Oct"},{"v":1162094400000,"label":"29Oct"},{"v":1162699200000,"label":"05Nov"},{"v":1163304000000,"label":"12Nov"},{"v":1163908800000,"label":"19Nov"},{"v":1164513600000,"label":"26Nov"}], Dygraph.dateTicker(1159675200000, 1164776400000, 500, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200000,"label":"01Oct"},{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"},{"v":1161489600000,"label":"22Oct"},{"v":1162094400000,"label":"29Oct"},{"v":1162699200000,"label":"05Nov"},{"v":1163304000000,"label":"12Nov"},{"v":1163908800000,"label":"19Nov"},{"v":1164513600000,"label":"26Nov"}], Dygraph.dateTicker(1159675200000, 1164776400000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1159675200962,"label":"01Oct"},{"v":1160280000962,"label":"08Oct"},{"v":1160884800962,"label":"15Oct"},{"v":1161489600962,"label":"22Oct"},{"v":1162094400962,"label":"29Oct"},{"v":1162699200962,"label":"05Nov"},{"v":1163304000962,"label":"12Nov"}], Dygraph.dateTicker(1160261979962, 1163905694248, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1160280000000,"label":"08Oct"},{"v":1160884800000,"label":"15Oct"}], Dygraph.dateTicker(1160539200000, 1161316800000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1161403200461,"label":"21Oct"},{"v":1161489600461,"label":"22Oct"},{"v":1161576000461,"label":"23Oct"},{"v":1161662400461,"label":"24Oct"},{"v":1161748800461,"label":"25Oct"},{"v":1161835200461,"label":"26Oct"},{"v":1161921600461,"label":"27Oct"},{"v":1162008000461,"label":"28Oct"}], Dygraph.dateTicker(1161489164461, 1162008465957, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1161554400860,"label":"18:00"},{"v":1161576000860,"label":"00:00"},{"v":1161597600860,"label":"06:00"},{"v":1161619200860,"label":"12:00"},{"v":1161640800860,"label":"18:00"}], Dygraph.dateTicker(1161575878860, 1161660991675, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1161770400840,"label":"06:00"},{"v":1161774000840,"label":"07:00"},{"v":1161777600840,"label":"08:00"},{"v":1161781200840,"label":"09:00"},{"v":1161784800840,"label":"10:00"},{"v":1161788400840,"label":"11:00"},{"v":1161792000840,"label":"12:00"}], Dygraph.dateTicker(1161770537840, 1161792063332, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1167627600000,"label":"01Jan"},{"v":1167714000000,"label":"02Jan"},{"v":1167800400000,"label":"03Jan"},{"v":1167886800000,"label":"04Jan"},{"v":1167973200000,"label":"05Jan"},{"v":1168059600000,"label":"06Jan"},{"v":1168146000000,"label":"07Jan"},{"v":1168232400000,"label":"08Jan"},{"v":1168318800000,"label":"09Jan"}], Dygraph.dateTicker(1167627600000, 1168318800000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1167627600000,"label":"Jan 07"}], Dygraph.dateTicker(1167627600000, 1199077200000, 100, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1167627600000,"label":"Jan 07"},{"v":1175400000000,"label":"Apr 07"},{"v":1183262400000,"label":"Jul 07"},{"v":1191211200000,"label":"Oct 07"}], Dygraph.dateTicker(1167627600000, 1199077200000, 300, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1167627600000,"label":"Jan 07"},{"v":1175400000000,"label":"Apr 07"},{"v":1183262400000,"label":"Jul 07"},{"v":1191211200000,"label":"Oct 07"}], Dygraph.dateTicker(1167627600000, 1199077200000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1167627600000,"label":"Jan 07"},{"v":1175400000000,"label":"Apr 07"},{"v":1183262400000,"label":"Jul 07"},{"v":1191211200000,"label":"Oct 07"}], Dygraph.dateTicker(1167627600000, 1199077200000, 600, this.createOptionsViewForAxis('x')));
-  // assertEquals([{"v":1167627600000,"label":"Jan 07"},{"v":1170306000000,"label":"Feb 07"},{"v":1172725200000,"label":"Mar 07"},{"v":1175400000000,"label":"Apr 07"},{"v":1177992000000,"label":"May 07"},{"v":1180670400000,"label":"Jun 07"},{"v":1183262400000,"label":"Jul 07"},{"v":1185940800000,"label":"Aug 07"},{"v":1188619200000,"label":"Sep 07"},{"v":1191211200000,"label":"Oct 07"},{"v":1193889600000,"label":"Nov 07"},{"v":1196485200000,"label":"Dec 07"},{"v":null,"label":"undefined NaN"}], Dygraph.dateTicker(1167627600000, 1199077200000, 800, this.createOptionsViewForAxis('x')));
-
-  // Test monthly for time span starting Dec 31, 2010.
-  assertEquals([{"v":1293858000000,"label":"Jan 11"},{"v":1296536400000,"label":"Feb 11"},{"v":1298955600000,"label":"Mar 11"},{"v":1301630400000,"label":"Apr 11"},{"v":1304222400000,"label":"May 11"},{"v":1306900800000,"label":"Jun 11"},{"v":1309492800000,"label":"Jul 11"},{"v":1312171200000,"label":"Aug 11"}], Dygraph.dateTicker(1293771600000, 1312862400000, 727, this.createOptionsViewForAxis('x')));
-
-  assertEquals([{"v":1201842000000,"label":"01Feb"},{"v":1201928400000,"label":"02Feb"},{"v":1202014800000,"label":"03Feb"},{"v":1202101200000,"label":"04Feb"},{"v":1202187600000,"label":"05Feb"},{"v":1202274000000,"label":"06Feb"}], Dygraph.dateTicker(1201842000000, 1202274000000, 700, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1210132800000,"label":"07May"},{"v":1210154400000,"label":"06:00"},{"v":1210176000000,"label":"12:00"},{"v":1210197600000,"label":"18:00"},{"v":1210219200000,"label":"08May"},{"v":1210240800000,"label":"06:00"},{"v":1210262400000,"label":"12:00"},{"v":1210284000000,"label":"18:00"},{"v":1210305600000,"label":"09May"}], Dygraph.dateTicker(1210132800000, 1210305600000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1210132800000,"label":"07May"},{"v":1210219200000,"label":"08May"},{"v":1210305600000,"label":"09May"},{"v":1210392000000,"label":"10May"},{"v":1210478400000,"label":"11May"}], Dygraph.dateTicker(1210132800000, 1210478400000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1210132800000,"label":"07May"},{"v":1210219200000,"label":"08May"},{"v":1210305600000,"label":"09May"},{"v":1210392000000,"label":"10May"},{"v":1210478400000,"label":"11May"},{"v":1210564800000,"label":"12May"}], Dygraph.dateTicker(1210132800000, 1210564800000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1214884800000,"label":"01Jul"},{"v":1214886600000,"label":"00:30"},{"v":1214888400000,"label":"01:00"},{"v":1214890200000,"label":"01:30"}], Dygraph.dateTicker(1214884800000, 1214891999000, 600, this.createOptionsViewForAxis('x')));
-  // assertEquals([{"v":1214884800000,"label":"Jul 08"},{"v":1217563200000,"label":"Aug 08"},{"v":1220241600000,"label":"Sep 08"},{"v":null,"label":"undefined NaN"}], Dygraph.dateTicker(1214884800000, 1222765200000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1215835200000,"label":"12Jul"},{"v":1215856800000,"label":"06:00"},{"v":1215878400000,"label":"12:00"},{"v":1215900000000,"label":"18:00"},{"v":1215921600000,"label":"13Jul"},{"v":1215943200000,"label":"06:00"},{"v":1215964800000,"label":"12:00"},{"v":1215986400000,"label":"18:00"}], Dygraph.dateTicker(1215835200000, 1216007940000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1246161600000,"label":"28Jun"},{"v":1246766400000,"label":"05Jul"},{"v":1247371200000,"label":"12Jul"},{"v":1247976000000,"label":"19Jul"}], Dygraph.dateTicker(1246420800000, 1248235200000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1246161600000,"label":"28Jun"},{"v":1246766400000,"label":"05Jul"},{"v":1247371200000,"label":"12Jul"},{"v":1247976000000,"label":"19Jul"},{"v":1248580800000,"label":"26Jul"},{"v":1249185600000,"label":"02Aug"}], Dygraph.dateTicker(1246420800000, 1249358400000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1247371200000,"label":"12Jul"},{"v":1247374800000,"label":"01:00"},{"v":1247378400000,"label":"02:00"},{"v":1247382000000,"label":"03:00"},{"v":1247385600000,"label":"04:00"},{"v":1247389200000,"label":"05:00"},{"v":1247392800000,"label":"06:00"}], Dygraph.dateTicker(1247371200000, 1247392800000, 600, this.createOptionsViewForAxis('x')));
-  // This one is DST-dependent:
-  // assertEquals([{"v":1247371200000,"label":"02:00"},{"v":1247374800000,"label":"03:00"},{"v":1247378400000,"label":"04:00"},{"v":1247382000000,"label":"05:00"},{"v":1247385600000,"label":"06:00"},{"v":1247389200000,"label":"07:00"},{"v":1247392800000,"label":"08:00"}], Dygraph.dateTicker(1247371200000, 1247392800000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1247371200000,"label":"12Jul"},{"v":1247374800000,"label":"01:00"},{"v":1247378400000,"label":"02:00"},{"v":1247382000000,"label":"03:00"},{"v":1247385600000,"label":"04:00"},{"v":1247389200000,"label":"05:00"},{"v":1247392800000,"label":"06:00"}], Dygraph.dateTicker(1247371200000, 1247392800000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1254196800000,"label":"29Sep"},{"v":1254283200000,"label":"30Sep"},{"v":1254369600000,"label":"01Oct"},{"v":1254456000000,"label":"02Oct"},{"v":1254542400000,"label":"03Oct"},{"v":1254628800000,"label":"04Oct"},{"v":1254715200000,"label":"05Oct"},{"v":1254801600000,"label":"06Oct"},{"v":1254888000000,"label":"07Oct"},{"v":1254974400000,"label":"08Oct"},{"v":1255060800000,"label":"09Oct"},{"v":1255147200000,"label":"10Oct"}], Dygraph.dateTicker(1254240000000, 1255190400000, 900, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1254369600000,"label":"01Oct"},{"v":1254456000000,"label":"02Oct"},{"v":1254542400000,"label":"03Oct"},{"v":1254628800000,"label":"04Oct"},{"v":1254715200000,"label":"05Oct"},{"v":1254801600000,"label":"06Oct"},{"v":1254888000000,"label":"07Oct"},{"v":1254974400000,"label":"08Oct"}], Dygraph.dateTicker(1254412800000, 1255017600000, 900, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1259643600000,"label":"01Dec"},{"v":1259730000000,"label":"02Dec"},{"v":1259816400000,"label":"03Dec"},{"v":1259902800000,"label":"04Dec"},{"v":1259989200000,"label":"05Dec"},{"v":1260075600000,"label":"06Dec"},{"v":1260162000000,"label":"07Dec"}], Dygraph.dateTicker(1259643600000, 1260162000000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1259643600000,"label":"01Dec"},{"v":1259730000000,"label":"02Dec"},{"v":1259816400000,"label":"03Dec"},{"v":1259902800000,"label":"04Dec"},{"v":1259989200000,"label":"05Dec"},{"v":1260075600000,"label":"06Dec"},{"v":1260162000000,"label":"07Dec"}], Dygraph.dateTicker(1259643600000, 1260162000000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1260075600000,"label":"06Dec"},{"v":1260680400000,"label":"13Dec"},{"v":1261285200000,"label":"20Dec"},{"v":1261890000000,"label":"27Dec"},{"v":1262494800000,"label":"03Jan"},{"v":1263099600000,"label":"10Jan"},{"v":1263704400000,"label":"17Jan"},{"v":1264309200000,"label":"24Jan"}], Dygraph.dateTicker(1260075600000, 1264309200000, 640, this.createOptionsViewForAxis('x')));
-  // assertEquals([{"v":1262322000000,"label":"Jan 10"},{"v":1265000400000,"label":"Feb 10"},{"v":1267419600000,"label":"Mar 10"},{"v":1270094400000,"label":"Apr 10"},{"v":null,"label":"undefined NaN"}], Dygraph.dateTicker(1262322000000, 1270872000000, 640, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1288929600000,"label":"05Nov"},{"v":1288951200000,"label":"06:00"},{"v":1288972800000,"label":"12:00"},{"v":1288994400000,"label":"18:00"},{"v":1289016000000,"label":"06Nov"},{"v":1289037600000,"label":"06:00"},{"v":1289059200000,"label":"12:00"},{"v":1289080800000,"label":"18:00"},{"v":1289102400000,"label":"07Nov"},{"v":1289124000000,"label":"05:00"},{"v":1289145600000,"label":"11:00"},{"v":1289167200000,"label":"17:00"},{"v":1289188800000,"label":"23:00"},{"v":1289210400000,"label":"05:00"},{"v":1289232000000,"label":"11:00"},{"v":1289253600000,"label":"17:00"},{"v":1289275200000,"label":"23:00"}], Dygraph.dateTicker(1288929600000, 1289278800000, 1024, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1291179600000,"label":"01Dec"},{"v":1291266000000,"label":"02Dec"},{"v":1291352400000,"label":"03Dec"},{"v":1291438800000,"label":"04Dec"},{"v":1291525200000,"label":"05Dec"},{"v":1291611600000,"label":"06Dec"},{"v":1291698000000,"label":"07Dec"},{"v":1291784400000,"label":"08Dec"},{"v":1291870800000,"label":"09Dec"}], Dygraph.dateTicker(1291179600000, 1291870800000, 600, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1294376400000,"label":"07Jan"},{"v":1294462800000,"label":"08Jan"},{"v":1294549200000,"label":"09Jan"},{"v":1294635600000,"label":"10Jan"},{"v":1294722000000,"label":"11Jan"},{"v":1294808400000,"label":"12Jan"},{"v":1294894800000,"label":"13Jan"},{"v":1294981200000,"label":"14Jan"}], Dygraph.dateTicker(1294376400000, 1294981200000, 480, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"}], Dygraph.dateTicker(1307922400112, 1307922450165, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"}], Dygraph.dateTicker(1307922400112, 1307922451166, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"}], Dygraph.dateTicker(1307922400112, 1307922452167, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"}], Dygraph.dateTicker(1307922400112, 1307922453167, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"}], Dygraph.dateTicker(1307922400112, 1307922454168, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"}], Dygraph.dateTicker(1307922400112, 1307922455169, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"}], Dygraph.dateTicker(1307922400112, 1307922456169, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"}], Dygraph.dateTicker(1307922400112, 1307922457170, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"}], Dygraph.dateTicker(1307922400112, 1307922458171, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"}], Dygraph.dateTicker(1307922400112, 1307922459172, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922460172, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922461174, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922462176, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922463177, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922464178, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922465112,"label":"19:47:45"}], Dygraph.dateTicker(1307922400112, 1307922465178, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922465112,"label":"19:47:45"}], Dygraph.dateTicker(1307922400112, 1307922466178, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922405112,"label":"19:46:45"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922415112,"label":"19:46:55"},{"v":1307922420112,"label":"19:47"},{"v":1307922425112,"label":"19:47:05"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922435112,"label":"19:47:15"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922445112,"label":"19:47:25"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922455112,"label":"19:47:35"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922465112,"label":"19:47:45"}], Dygraph.dateTicker(1307922400112, 1307922467179, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922468179, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"}], Dygraph.dateTicker(1307922400112, 1307922469179, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922470180, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922471180, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922472181, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922473181, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922474182, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922475182, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922476183, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922477183, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922478184, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"}], Dygraph.dateTicker(1307922400112, 1307922479185, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922480186, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922481187, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922482188, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922483188, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922484189, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922485190, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922486191, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922487192, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922488192, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"}], Dygraph.dateTicker(1307922400112, 1307922489193, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922490194, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922491194, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922492196, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922493196, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922494197, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922495197, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922496198, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922497199, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922498200, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"}], Dygraph.dateTicker(1307922400112, 1307922499200, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922500201, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922501201, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922502202, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922503203, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922504204, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922505205, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922506205, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922507206, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922508209, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"}], Dygraph.dateTicker(1307922400112, 1307922509209, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922510209, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922511210, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922512211, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922513211, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922514212, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922515213, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922516214, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922517214, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922518215, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922519215, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922520217, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922521218, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922522219, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922523219, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922524220, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922525221, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922526222, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922527222, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922528223, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"}], Dygraph.dateTicker(1307922400112, 1307922529223, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"},{"v":1307922530112,"label":"19:48:50"}], Dygraph.dateTicker(1307922400112, 1307922530224, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"},{"v":1307922530112,"label":"19:48:50"}], Dygraph.dateTicker(1307922400112, 1307922531225, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"},{"v":1307922530112,"label":"19:48:50"}], Dygraph.dateTicker(1307922400112, 1307922532226, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"},{"v":1307922530112,"label":"19:48:50"}], Dygraph.dateTicker(1307922400112, 1307922533227, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922400112,"label":"19:46:40"},{"v":1307922410112,"label":"19:46:50"},{"v":1307922420112,"label":"19:47"},{"v":1307922430112,"label":"19:47:10"},{"v":1307922440112,"label":"19:47:20"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922460112,"label":"19:47:40"},{"v":1307922470112,"label":"19:47:50"},{"v":1307922480112,"label":"19:48"},{"v":1307922490112,"label":"19:48:10"},{"v":1307922500112,"label":"19:48:20"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922520112,"label":"19:48:40"},{"v":1307922530112,"label":"19:48:50"}], Dygraph.dateTicker(1307922400112, 1307922534227, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922535227, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922536228, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922537230, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922538231, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"}], Dygraph.dateTicker(1307922400112, 1307922539232, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922540233, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922541233, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922542234, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922543240, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922544240, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922545240, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922546241, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922547241, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922548242, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922549243, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922550243, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922551244, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922552245, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922553245, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922554246, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922555247, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922556247, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922557248, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922558249, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922559250, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922560251, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922561252, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922562252, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922563253, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922564254, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922565254, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922566255, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922567256, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922568256, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"}], Dygraph.dateTicker(1307922400112, 1307922569257, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"},{"v":1307922570112,"label":"19:49:30"}], Dygraph.dateTicker(1307922400112, 1307922570258, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"},{"v":1307922570112,"label":"19:49:30"}], Dygraph.dateTicker(1307922400112, 1307922571258, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"},{"v":1307922570112,"label":"19:49:30"}], Dygraph.dateTicker(1307922400112, 1307922572259, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":1307922390112,"label":"19:46:30"},{"v":1307922420112,"label":"19:47"},{"v":1307922450112,"label":"19:47:30"},{"v":1307922480112,"label":"19:48"},{"v":1307922510112,"label":"19:48:30"},{"v":1307922540112,"label":"19:49"},{"v":1307922570112,"label":"19:49:30"}], Dygraph.dateTicker(1307922400112, 1307922573260, 800, this.createOptionsViewForAxis('x')));
-  assertEquals([{"v":978325200000,"label":"Jan 01"},{"v":986101200000,"label":"Apr 01"},{"v":993960000000,"label":"Jul 01"},{"v":1001908800000,"label":"Oct 01"}], Dygraph.dateTicker(978325200000, 1001908800000, 400, this.createOptionsViewForAxis('x')));
-};
-
-TickerTestCase.prototype.testAllNumericTickers = function() {
-  assertEquals([{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(-0.4, 4.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-1.5,"label":"-1.5"},{"v":-1,"label":"-1"},{"v":-0.5,"label":"-0.5"},{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"}], Dygraph.numericTicks(-1.4157430939856124, 1.4157430939856124, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-10,"label":"-10"},{"v":-8,"label":"-8"},{"v":-6,"label":"-6"},{"v":-4,"label":"-4"},{"v":-2,"label":"-2"},{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"}], Dygraph.numericTicks(-10, 9.98046875, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-200,"label":"-200"},{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":1000,"label":"1000"}], Dygraph.numericTicks(-101.10000000000001, 1100.1, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"}], Dygraph.numericTicks(-11.687459005175139, 42.287459005175144, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-12, 12, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-15,"label":"-15"},{"v":-10,"label":"-10"},{"v":-5,"label":"-5"},{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"}], Dygraph.numericTicks(-13.19792086872138, 13.197062407353386, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-220,"label":"-220"},{"v":-200,"label":"-200"},{"v":-180,"label":"-180"},{"v":-160,"label":"-160"},{"v":-140,"label":"-140"},{"v":-120,"label":"-120"}], Dygraph.numericTicks(-220, -100, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(-32.8, 132.8, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-40,"label":"-40"},{"v":-30,"label":"-30"},{"v":-20,"label":"-20"},{"v":-10,"label":"-10"},{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(-34.309, 89.279, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":-60,"label":"-60"},{"v":-40,"label":"-40"},{"v":-20,"label":"-20"},{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"}], Dygraph.numericTicks(-60, 60, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":"2.00e-4"},{"v":0.00030000000000000003,"label":"3.00e-4"},{"v":0.0004,"label":"4.00e-4"},{"v":0.0005,"label":"5.00e-4"}], Dygraph.numericTicks(0, 0.00055, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":0},{"v":0.0001,"label":0.0001},{"v":0.0002,"label":0.0002},{"v":0.00030000000000000003,"label":0.0003},{"v":0.0004,"label":0.0004},{"v":0.0005,"label":0.0005}], Dygraph.numericTicks(0, 0.00055, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"}], Dygraph.numericTicks(0, 1, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.1,"label":"0.1"},{"v":0.2,"label":"0.2"},{"v":0.30000000000000004,"label":"0.3"},{"v":0.4,"label":"0.4"},{"v":0.5,"label":"0.5"},{"v":0.6000000000000001,"label":"0.6"},{"v":0.7000000000000001,"label":"0.7"},{"v":0.8,"label":"0.8"},{"v":0.9,"label":"0.9"},{"v":1,"label":"1"},{"v":1.1,"label":"1.1"},{"v":1.2000000000000002,"label":"1.2"}], Dygraph.numericTicks(0, 1.2, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 100, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 104.53192180924043, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 109.9856877755916, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(0, 11, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 110, 350, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":1000,"label":"1000"}], Dygraph.numericTicks(0, 1100, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1000000,"label":"1M"},{"v":2000000,"label":"2M"},{"v":3000000,"label":"3M"},{"v":4000000,"label":"4M"},{"v":5000000,"label":"5M"},{"v":6000000,"label":"6M"},{"v":7000000,"label":"7M"},{"v":8000000,"label":"8M"},{"v":9000000,"label":"9M"},{"v":10000000,"label":"10M"}], Dygraph.numericTicks(0, 11000000, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(0, 119, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 130.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"}], Dygraph.numericTicks(0, 131, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":2000,"label":"2000"},{"v":4000,"label":"4000"},{"v":6000,"label":"6000"},{"v":8000,"label":"8000"},{"v":10000,"label":"10000"},{"v":12000,"label":"12000"},{"v":14000,"label":"14000"},{"v":16000,"label":"16000"}], Dygraph.numericTicks(0, 16977.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"}], Dygraph.numericTicks(0, 2, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.2,"label":"0.2"},{"v":0.4,"label":"0.4"},{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":1.2000000000000002,"label":"1.2"},{"v":1.4000000000000001,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":1.8,"label":"1.8"}], Dygraph.numericTicks(0, 2, 400, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"}], Dygraph.numericTicks(0, 2.2, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":200000,"label":"200K"},{"v":400000,"label":"400K"},{"v":600000,"label":"600K"},{"v":800000,"label":"800K"},{"v":1000000,"label":"1M"},{"v":1200000,"label":"1.2M"},{"v":1400000,"label":"1.4M"},{"v":1600000,"label":"1.6M"},{"v":1800000,"label":"1.8M"},{"v":2000000,"label":"2M"}], Dygraph.numericTicks(0, 2200000, 350, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":0,"label":"0"},{"v":50,"label":"50"},{"v":100,"label":"100"},{"v":150,"label":"150"},{"v":200,"label":"200"}], Dygraph.numericTicks(0, 249, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":500,"label":"500"},{"v":1000,"label":"1000"},{"v":1500,"label":"1500"},{"v":2000,"label":"2000"},{"v":2500,"label":"2500"}], Dygraph.numericTicks(0, 2747.9970998900817, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"},{"v":1000,"label":"1K"},{"v":1200,"label":"1.2K"},{"v":1400,"label":"1.4K"},{"v":1600,"label":"1.6K"},{"v":1800,"label":"1.8K"},{"v":2000,"label":"2K"},{"v":2200,"label":"2.2K"},{"v":2400,"label":"2.4K"},{"v":2600,"label":"2.6K"}], Dygraph.numericTicks(0, 2747.9970998900817, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 32.698942321287205, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":500000,"label":"500000"},{"v":1000000,"label":"1.00e+6"},{"v":1500000,"label":"1.50e+6"},{"v":2000000,"label":"2.00e+6"},{"v":2500000,"label":"2.50e+6"},{"v":3000000,"label":"3.00e+6"}], Dygraph.numericTicks(0, 3263100.6418021005, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"}], Dygraph.numericTicks(0, 33.16213467701236, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(0, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0, 4.4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":0,"label":"0"},{"v":8,"label":"8"},{"v":16,"label":"16"},{"v":24,"label":"24"},{"v":32,"label":"32"},{"v":40,"label":"40"}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":0},{"v":8,"label":8},{"v":16,"label":16},{"v":24,"label":24},{"v":32,"label":32},{"v":40,"label":40}], Dygraph.numericTicks(0, 42, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1000000000000,"label":"1T"},{"v":2000000000000,"label":"2T"},{"v":3000000000000,"label":"3T"},{"v":4000000000000,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":0,"label":"0"},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":0},{"v":549755813888,"label":"512G"},{"v":1099511627776,"label":"1T"},{"v":1649267441664,"label":"1.5T"},{"v":2199023255552,"label":"2T"},{"v":2748779069440,"label":"2.5T"},{"v":3298534883328,"label":"3T"},{"v":3848290697216,"label":"3.5T"},{"v":4398046511104,"label":"4T"}], Dygraph.numericTicks(0, 4837851162214.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":true,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1000,"label":"1000"},{"v":2000,"label":"2000"},{"v":3000,"label":"3000"},{"v":4000,"label":"4000"},{"v":5000,"label":"5000"}], Dygraph.numericTicks(0, 5451.6, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"}], Dygraph.numericTicks(0, 550, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"}], Dygraph.numericTicks(0, 64.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":"300"},{"v":400,"label":"400"},{"v":500,"label":"500"},{"v":600,"label":"600"}], Dygraph.numericTicks(0, 667.9, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.7, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(0, 7.9347329768293005, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"}], Dygraph.numericTicks(0, 72.6, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"}], Dygraph.numericTicks(0, 99, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(0, 99, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":200,"label":"200"},{"v":400,"label":"400"},{"v":600,"label":"600"},{"v":800,"label":"800"}], Dygraph.numericTicks(0, 999, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.000001,"label":"1.00e-6"},{"v":0.000002,"label":""},{"v":0.000003,"label":""},{"v":0.000004,"label":""},{"v":0.0000049999999999999996,"label":""},{"v":0.000006,"label":""},{"v":0.000007,"label":""},{"v":0.000008,"label":""},{"v":0.000009,"label":""},{"v":0.00001,"label":"1.00e-5"},{"v":0.00002,"label":""},{"v":0.000030000000000000004,"label":""},{"v":0.00004,"label":""},{"v":0.00005,"label":""},{"v":0.00006000000000000001,"label":""},{"v":0.00007000000000000001,"label":""},{"v":0.00008,"label":""},{"v":0.00009,"label":""},{"v":0.0001,"label":"1.00e-4"},{"v":0.0002,"label":""},{"v":0.00030000000000000003,"label":""},{"v":0.0004,"label":""},{"v":0.0005,"label":""},{"v":0.0006000000000000001,"label":""},{"v":0.0007,"label":""},{"v":0.0008,"label":""},{"v":0.0009000000000000001,"label":""},{"v":0.001,"label":"1.00e-3"},{"v":0.002,"label":""},{"v":0.003,"label":""},{"v":0.004,"label":""},{"v":0.005,"label":""},{"v":0.006,"label":""},{"v":0.007,"label":""},{"v":0.008,"label":""},{"v":0.009000000000000001,"label":""},{"v":0.01,"label":"0.01"},{"v":0.02,"label":""},{"v":0.03,"label":""},{"v":0.04,"label":""},{"v":0.05,"label":""},{"v":0.06,"label":""},{"v":0.07,"label":""},{"v":0.08,"label":""},{"v":0.09,"label":""},{"v":0.1,"label":"0.1"},{"v":0.2,"label":""},{"v":0.30000000000000004,"label":""},{"v":0.4,"label":""},{"v":0.5,"label":""},{"v":0.6000000000000001,"label":""},{"v":0.7000000000000001,"label":""},{"v":0.8,"label":""},{"v":0.9,"label":""},{"v":1,"label":"1"},{"v":2,"label":""},{"v":3,"label":""},{"v":4,"label":""},{"v":5,"label":""},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":""},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":""},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":1000,"label":"1000"}], Dygraph.numericTicks(0.000001, 1099.9999999, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(0.6, 5.4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 353, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.6000000000000001,"label":"0.6"},{"v":0.8,"label":"0.8"},{"v":1,"label":"1"},{"v":1.2000000000000002,"label":"1.2"},{"v":1.4000000000000001,"label":"1.4"},{"v":1.6,"label":"1.6"},{"v":1.8000000000000003,"label":"1.8"},{"v":2,"label":"2"},{"v":2.2,"label":"2.2"},{"v":2.4000000000000004,"label":"2.4"},{"v":2.6,"label":"2.6"},{"v":2.8000000000000003,"label":"2.8"},{"v":3.0000000000000004,"label":"3"},{"v":3.2,"label":"3.2"},{"v":3.4000000000000004,"label":"3.4"},{"v":3.6,"label":"3.6"},{"v":3.8000000000000003,"label":"3.8"},{"v":4,"label":"4"},{"v":4.2,"label":"4.2"},{"v":4.4,"label":"4.4"},{"v":4.6,"label":"4.6"},{"v":4.800000000000001,"label":"4.8"}], Dygraph.numericTicks(0.6373123361267239, 4.824406504982038, 743, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(0.6386658954698001, 4.8095173522082, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0.5,"label":"0.5"},{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"}], Dygraph.numericTicks(0.7101014279158788, 4.023726495301334, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20,"label":"20"},{"v":40,"label":"40"},{"v":60,"label":"60"},{"v":80,"label":"80"},{"v":100,"label":"100"}], Dygraph.numericTicks(1, 109, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"}], Dygraph.numericTicks(1, 3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(1, 4, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1, 5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":1},{"v":1.5,"label":1.5},{"v":2,"label":2},{"v":2.5,"label":2.5},{"v":3,"label":3},{"v":3.5,"label":3.5},{"v":4,"label":4},{"v":4.5,"label":4.5}], Dygraph.numericTicks(1, 5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"}], Dygraph.numericTicks(1, 6, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"}], Dygraph.numericTicks(1, 7, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 300, this.createOptionsViewForAxis('y',{"logscale":false,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":"7"},{"v":8,"label":""},{"v":9,"label":"9"}], Dygraph.numericTicks(1, 9, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"},{"v":8,"label":"8"}], Dygraph.numericTicks(1, 9, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":2,"label":"2"},{"v":4,"label":"4"},{"v":6,"label":"6"},{"v":8,"label":"8"},{"v":10,"label":"10"}], Dygraph.numericTicks(1.2, 10.8, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":1.5,"label":"1.5"},{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(1.2872947778969237, 4.765317192093838, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":1,"label":"1"},{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(1.5, 7.5, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":5,"label":"5"},{"v":10,"label":"10"},{"v":15,"label":"15"},{"v":20,"label":"20"},{"v":25,"label":"25"}], Dygraph.numericTicks(1.7999999999999998, 28.2, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":10,"label":"10"},{"v":10.1,"label":"10.1"},{"v":10.2,"label":"10.2"},{"v":10.3,"label":"10.3"},{"v":10.4,"label":"10.4"},{"v":10.5,"label":"10.5"},{"v":10.6,"label":"10.6"},{"v":10.7,"label":"10.7"},{"v":10.8,"label":"10.8"},{"v":10.9,"label":"10.9"}], Dygraph.numericTicks(10, 11, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"}], Dygraph.numericTicks(100, 200, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":10000,"label":"10000"},{"v":12000,"label":"12000"},{"v":14000,"label":"14000"},{"v":16000,"label":"16000"},{"v":18000,"label":"18000"},{"v":20000,"label":"20000"},{"v":22000,"label":"22000"},{"v":24000,"label":"24000"},{"v":26000,"label":"26000"},{"v":28000,"label":"28000"},{"v":30000,"label":"30000"},{"v":32000,"label":"32000"},{"v":34000,"label":"34000"},{"v":36000,"label":"36000"}], Dygraph.numericTicks(10122.8, 36789.2, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":11000,"label":"11000"},{"v":11200,"label":"11200"},{"v":11400,"label":"11400"},{"v":11600,"label":"11600"},{"v":11800,"label":"11800"},{"v":12000,"label":"12000"},{"v":12200,"label":"12200"},{"v":12400,"label":"12400"},{"v":12600,"label":"12600"},{"v":12800,"label":"12800"},{"v":13000,"label":"13000"},{"v":13200,"label":"13200"},{"v":13400,"label":"13400"}], Dygraph.numericTicks(11110.5, 13579.5, 480, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":162000,"label":"162000"},{"v":164000,"label":"164000"},{"v":166000,"label":"166000"},{"v":168000,"label":"168000"},{"v":170000,"label":"170000"},{"v":172000,"label":"172000"},{"v":174000,"label":"174000"},{"v":176000,"label":"176000"},{"v":178000,"label":"178000"}], Dygraph.numericTicks(163038.4, 179137.6, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":2,"label":"2"},{"v":2.5,"label":"2.5"},{"v":3,"label":"3"},{"v":3.5,"label":"3.5"}], Dygraph.numericTicks(2, 4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":2,"label":"2"},{"v":3,"label":"3"},{"v":4,"label":"4"},{"v":5,"label":"5"},{"v":6,"label":"6"},{"v":7,"label":"7"}], Dygraph.numericTicks(2.6, 7.4, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(21.7, 97.3, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(24, 96, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 20, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20})));
-  assertEquals([{"v":25,"label":"25"},{"v":30,"label":"30"},{"v":35,"label":"35"},{"v":40,"label":"40"},{"v":45,"label":"45"},{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"},{"v":85,"label":"85"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false,pixelsPerLabel:20})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(26.185714285714287, 90.81428571428572, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":20,"label":"20"},{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(28.33333333333333, 88.33333333333334, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":3,"label":"3"},{"v":3.5,"label":"3.5"},{"v":4,"label":"4"},{"v":4.5,"label":"4.5"}], Dygraph.numericTicks(3, 5, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":3000,"label":"3K"},{"v":2500,"label":"2.5K"},{"v":2000,"label":"2K"},{"v":1500,"label":"1.5K"},{"v":1000,"label":"1K"},{"v":500,"label":"500"}], Dygraph.numericTicks(3000, 0, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(33.11333333333334, 83.75333333333333, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":30,"label":"30"},{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"}], Dygraph.numericTicks(36.921241050119335, 88.32696897374701, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":50,"label":""},{"v":60,"label":"60"},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":""},{"v":300,"label":"300"},{"v":400,"label":""},{"v":500,"label":""},{"v":600,"label":"600"},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":1000,"label":"1000"},{"v":2000,"label":""},{"v":3000,"label":"3000"},{"v":4000,"label":""},{"v":5000,"label":""},{"v":6000,"label":"6000"},{"v":7000,"label":""},{"v":8000,"label":""},{"v":9000,"label":""},{"v":10000,"label":"10000"}], Dygraph.numericTicks(41.220000000000084, 15576.828000000018, 400, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":40,"label":"40"},{"v":50,"label":"50"},{"v":60,"label":"60"},{"v":70,"label":"70"},{"v":80,"label":"80"},{"v":90,"label":"90"}], Dygraph.numericTicks(44.5, 98.5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":5,"label":"5"},{"v":6,"label":""},{"v":7,"label":""},{"v":8,"label":""},{"v":9,"label":""},{"v":10,"label":"10"},{"v":20,"label":"20"},{"v":30,"label":""},{"v":40,"label":""},{"v":50,"label":"50"},{"v":60,"label":""},{"v":70,"label":""},{"v":80,"label":""},{"v":90,"label":""},{"v":100,"label":"100"},{"v":200,"label":"200"},{"v":300,"label":""},{"v":400,"label":""},{"v":500,"label":"500"},{"v":600,"label":""},{"v":700,"label":""},{"v":800,"label":""},{"v":900,"label":""},{"v":1000,"label":"1000"}], Dygraph.numericTicks(5, 1099.5, 300, this.createOptionsViewForAxis('y',{"logscale":true,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":50,"label":"50"},{"v":55,"label":"55"},{"v":60,"label":"60"},{"v":65,"label":"65"},{"v":70,"label":"70"},{"v":75,"label":"75"},{"v":80,"label":"80"}], Dygraph.numericTicks(52.5, 82.5, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":68,"label":"68"},{"v":70,"label":"70"},{"v":72,"label":"72"},{"v":74,"label":"74"},{"v":76,"label":"76"},{"v":78,"label":"78"},{"v":80,"label":"80"}], Dygraph.numericTicks(69, 81, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":0,"label":"0"},{"v":20000,"label":"20K"},{"v":40000,"label":"40K"},{"v":60000,"label":"60K"},{"v":80000,"label":"80K"}], Dygraph.numericTicks(7921.099999999999, 81407.9, 240, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 300, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":8,"label":"8"},{"v":10,"label":"10"},{"v":12,"label":"12"},{"v":14,"label":"14"},{"v":16,"label":"16"},{"v":18,"label":"18"},{"v":20,"label":"20"}], Dygraph.numericTicks(9, 21, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":9,"label":"9"},{"v":10,"label":"10"},{"v":11,"label":"11"},{"v":12,"label":"12"},{"v":13,"label":"13"},{"v":14,"label":"14"},{"v":15,"label":"15"},{"v":16,"label":"16"},{"v":17,"label":"17"},{"v":18,"label":"18"}], Dygraph.numericTicks(9.2, 18.8, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":80,"label":"80"},{"v":100,"label":"100"},{"v":120,"label":"120"},{"v":140,"label":"140"},{"v":160,"label":"160"},{"v":180,"label":"180"},{"v":200,"label":"200"}], Dygraph.numericTicks(90, 210, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":true})));
-  assertEquals([{"v":95,"label":"95"},{"v":96,"label":"96"},{"v":97,"label":"97"},{"v":98,"label":"98"},{"v":99,"label":"99"},{"v":100,"label":"100"},{"v":101,"label":"101"},{"v":102,"label":"102"},{"v":103,"label":"103"},{"v":104,"label":"104"}], Dygraph.numericTicks(95.71121718377088, 104.23150357995226, 320, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-  assertEquals([{"v":950,"label":"950"},{"v":1000,"label":"1000"},{"v":1050,"label":"1050"},{"v":1100,"label":"1100"},{"v":1150,"label":"1150"},{"v":1200,"label":"1200"}], Dygraph.numericTicks(980.1, 1218.9, 200, this.createOptionsViewForAxis('y',{"logscale":null,"labelsKMG2":false,"labelsKMB":false})));
-};
index 22ee3fe..70518a1 100644 (file)
@@ -172,19 +172,25 @@ ToDomCoordsTestCase.prototype.testAxisTickSize = function() {
   assertEquals([500, 386], g.toDomCoords(100, 0));
 }
 
-ToDomCoordsTestCase.prototype.testChartLogarithmic = function() {
+ToDomCoordsTestCase.prototype.testChartLogarithmic_YAxis = function() {
   var opts = {
-    drawXAxis: false,
-    drawYAxis: false,
-    drawXGrid: false,
-    drawYGrid: false,
-    logscale: true,
     rightGap: 0,
     valueRange: [1, 4],
     dateWindow: [0, 10],
     width: 400,
     height: 400,
-    colors: ['#ff0000']
+    colors: ['#ff0000'],
+    axes: {
+      x: {
+        drawGrid: false,
+        drawAxis: false
+      },
+      y: {
+        drawGrid: false,
+        drawAxis: false,
+        logscale: true
+      }
+    }
   }
 
   var graph = document.getElementById("graph");
@@ -203,3 +209,64 @@ ToDomCoordsTestCase.prototype.testChartLogarithmic = function() {
   assertEquals([400, 400], g.toDomCoords(10, 1));
   assertEquals([400, 200], g.toDomCoords(10, 2));
 }
+
+ToDomCoordsTestCase.prototype.testChartLogarithmic_XAxis = function() {
+  var opts = {
+    rightGap: 0,
+    valueRange: [1, 1000],
+    dateWindow: [1, 1000],
+    width: 400,
+    height: 400,
+    colors: ['#ff0000'],
+    axes: {
+      x: {
+        drawGrid: false,
+        drawAxis: false,
+        logscale: true
+      },
+      y: {
+        drawGrid: false,
+        drawAxis: false
+      }
+    }
+  }
+
+  var graph = document.getElementById("graph");
+  g = new Dygraph(graph, [ [1,1], [10, 10], [100,100], [1000,1000] ], opts);
+
+  var epsilon = 1e-8;
+  assertEqualsDelta(1, g.toDataXCoord(0), epsilon);
+  assertEqualsDelta(5.623413251903489, g.toDataXCoord(100), epsilon);
+  assertEqualsDelta(31.62277660168378, g.toDataXCoord(200), epsilon);
+  assertEqualsDelta(177.8279410038921, g.toDataXCoord(300), epsilon);
+  assertEqualsDelta(1000, g.toDataXCoord(400), epsilon);
+
+  assertEqualsDelta(0, g.toDomXCoord(1), epsilon);
+  assertEqualsDelta(3.6036036036036037, g.toDomXCoord(10), epsilon);
+  assertEqualsDelta(39.63963963963964, g.toDomXCoord(100), epsilon);
+  assertEqualsDelta(400, g.toDomXCoord(1000), epsilon);
+
+  assertEqualsDelta(0, g.toPercentXCoord(1), epsilon);
+  assertEqualsDelta(0.3333333333, g.toPercentXCoord(10), epsilon);
+  assertEqualsDelta(0.6666666666, g.toPercentXCoord(100), epsilon);
+  assertEqualsDelta(1, g.toPercentXCoord(1000), epsilon);
+  // Now zoom in and ensure that the methods return reasonable values.
+  g.updateOptions({dateWindow: [ 10, 100 ]});
+
+  assertEqualsDelta(10, g.toDataXCoord(0), epsilon);
+  assertEqualsDelta(17.78279410038923, g.toDataXCoord(100), epsilon);
+  assertEqualsDelta(31.62277660168379, g.toDataXCoord(200), epsilon);
+  assertEqualsDelta(56.23413251903491, g.toDataXCoord(300), epsilon);
+  assertEqualsDelta(100, g.toDataXCoord(400), epsilon);
+
+  assertEqualsDelta(-40, g.toDomXCoord(1), epsilon);
+  assertEqualsDelta(0, g.toDomXCoord(10), epsilon);
+  assertEqualsDelta(400, g.toDomXCoord(100), epsilon);
+  assertEqualsDelta(4400, g.toDomXCoord(1000), epsilon);
+
+  assertEqualsDelta(-1, g.toPercentXCoord(1), epsilon);
+  assertEqualsDelta(0, g.toPercentXCoord(10), epsilon);
+  assertEqualsDelta(1, g.toPercentXCoord(100), epsilon);
+  assertEqualsDelta(2, g.toPercentXCoord(1000), epsilon);
+}
\ No newline at end of file
index ac28e99..faf8f25 100644 (file)
@@ -67,7 +67,7 @@ UpdateOptionsTestCase.prototype.testStrokeSingleSeries = function() {
   var optionsForY1 = { };
 
   optionsForY1['strokeWidth'] = 3;
-  updatedOptions['Y1'] = optionsForY1;
+  updatedOptions['series'] = {'Y1': optionsForY1};
 
   // These options will allow us to jump to renderGraph_()
   // drawGraph_() will be skipped.
@@ -80,17 +80,16 @@ UpdateOptionsTestCase.prototype.testStrokeSingleSeries = function() {
 UpdateOptionsTestCase.prototype.testSingleSeriesRequiresNewPoints = function() {
   var graphDiv = document.getElementById("graph");
   var graph = new Dygraph(graphDiv, this.data, this.opts);
-  var updatedOptions = { };
-  var optionsForY1 = { };
-  var optionsForY2 = { };
-
-  // This will not require new points.
-  optionsForY1['strokeWidth'] = 2;
-  updatedOptions['Y1'] = optionsForY1;
-
-  // This will require new points.
-  optionsForY2['stepPlot'] = true;
-  updatedOptions['Y2'] = optionsForY2;
+  var updatedOptions = {
+    series: {
+      Y1: {
+        strokeWidth: 2
+      },
+      Y2: {
+        stepPlot: true
+      }
+    }
+  };
 
   // These options will not allow us to jump to renderGraph_()
   // drawGraph_() must be called
index 0aee0d5..5bd7f8d 100644 (file)
@@ -177,6 +177,12 @@ UtilsTestCase.prototype.testToRGB = function() {
   assertEquals({r: 255, g: 0, b: 0}, Dygraph.toRGB_('red'));
 };
 
+UtilsTestCase.prototype.testIsPixelChangingOptionList = function() {
+  var isPx = Dygraph.isPixelChangingOptionList;
+  assertTrue(isPx([], { axes: { y: { digitsAfterDecimal: 3 }}}));
+  assertFalse(isPx([], { axes: { y: { axisLineColor: 'blue' }}}));
+};
+
 /*
 UtilsTestCase.prototype.testDateSet = function() {
   var base = new Date(1383455100000);
diff --git a/bower.json b/bower.json
new file mode 100644 (file)
index 0000000..2b21425
--- /dev/null
@@ -0,0 +1,43 @@
+{
+  "name": "dygraphs",
+  "version": "v1.0.1+bower",
+  "main": "dygraph-combined.js",
+  "ignore": [
+    "Makefile",
+    "NOTES",
+    "auto_tests",
+    "closure-todo.txt",
+    "common",
+    "compile-with-closure.sh",
+    "dashed-canvas.js",
+    "data.js",
+    "datahandler",
+    "docs",
+    "experimental",
+    "file-size-stats.sh",
+    "gadget.xml",
+    "gallery",
+    "generate-combined.sh",
+    "generate-documentation.py",
+    "generate-download.py",
+    "generate-jar.sh",
+    "generate-jsdoc.sh",
+    "gviz-api.js",
+    "gwt",
+    "jsTestDriver.conf",
+    "jsdoc-toolkit",
+    "lint.sh",
+    "node_modules",
+    "package.json",
+    "phantom-driver.js",
+    "phantom-perf.js",
+    "push-to-web.sh",
+    "release.sh",
+    "releases.json",
+    "screenshot.png",
+    "test.sh",
+    "tests",
+    "thumbnail.png",
+    "yuicompressor-2.4.2.jar"
+  ]
+}
diff --git a/check-combined-unaffected.sh b/check-combined-unaffected.sh
new file mode 100755 (executable)
index 0000000..de1721e
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Ensures that dygraph-combined.js is unaffected.
+# Helpful for pull requests, where this is a common mistake.
+
+grep 'var' dygraph-combined.js > /dev/null
+if [ $? -eq 0 ]; then
+  echo 'Please revert changes to dygraph-combined.js' >&2
+  echo 'You can do this by running:  ' >& 2
+  echo '' >& 2
+  echo '    git checkout dygraph-combined.js' >&2
+  echo '' >& 2
+  exit 1
+fi
+
+exit 0
index 496a244..90d9d9a 100755 (executable)
@@ -6,22 +6,18 @@
 # It outputs minified JS to a temp file. This should be ignored for now, until
 # it's fully functional.
 
-if [ $# -ne 1 ]; then
-  echo "Usage: $0 (path/to/closure/compiler.jar)" 1>&2
+CLOSURE_COMPILER=node_modules/closure-compiler/lib/vendor/compiler.jar
+BASE_JS=node_modules/obvious-closure-library/closure/goog/base.js
+if [[ (! -f $CLOSURE_COMPILER) || (! -f $BASE_JS) ]]; then
+  echo "Missing compiler.jar or base.js. Try running 'npm install'." 1>&2
   exit 1
 fi
-if [ ! -f $1 ]; then
-  echo "$1 does not exist"
-  exit 1
-fi
-
-CLOSURE_COMPILER=$1
 
 java -jar $CLOSURE_COMPILER \
  --compilation_level ADVANCED_OPTIMIZATIONS  \
  --warning_level VERBOSE  \
  --output_wrapper='(function() {%output%})();'  \
- --js ../../closure-library-read-only/closure/goog/base.js \
+ --js $BASE_JS \
  --js=dashed-canvas.js \
  --js=dygraph-options.js \
  --js=dygraph-layout.js \
index cec7729..0fc6874 100644 (file)
@@ -4,6 +4,9 @@
  * MIT-licensed (http://opensource.org/licenses/MIT)
  */
 
+(function() {
+'use strict';
+
 /**
  * @fileoverview Adds support for dashed lines to the HTML5 canvas.
  *
@@ -46,8 +49,6 @@
  * as a smaller even length array.
  */
 CanvasRenderingContext2D.prototype.installPattern = function(pattern) {
-  "use strict";
-
   if (typeof(this.isPatternInstalled) !== 'undefined') {
     throw "Must un-install old line pattern before installing a new one.";
   }
@@ -157,7 +158,8 @@ CanvasRenderingContext2D.prototype.installPattern = function(pattern) {
         }
 
         this.restore();
-        x1 = x2, y1 = y2;
+        x1 = x2;
+        y1 = y2;
       }
     }
     realStroke.call(this);
@@ -173,4 +175,6 @@ CanvasRenderingContext2D.prototype.installPattern = function(pattern) {
 CanvasRenderingContext2D.prototype.uninstallPattern = function() {
   // This will be replaced by a non-error version when a pattern is installed.
   throw "Must install a line pattern before uninstalling it.";
-}
+};
+
+})();
index 2b637b5..b3eae91 100644 (file)
@@ -103,7 +103,8 @@ handler.prototype.extractSeries = function(rawData, seriesIndex, options) {
 };
 
 /**
- * Converts a series to a Point array.
+ * Converts a series to a Point array.  The resulting point array must be
+ * returned in increasing order of idx property.
  * 
  * @param {!Array.<[!number,?number,?]>} series The series in the unified 
  *          data format where series[i] = [x,y,{extras}].
index 689e1e2..37c2e90 100644 (file)
@@ -76,8 +76,8 @@
             <ul id="menu3" class="dropdown-menu" role="menu" aria-labelledby="drop6">
               <li role="presentation"><a role="menuitem" tabindex="-1" href="changes.html">Contributors Guide</a></li>
               <li role="presentation"><a role="menuitem" tabindex="-1" href="http://github.com/danvk/dygraphs">Source (Github)</a></li>
-              <li role="presentation"><a role="menuitem" tabindex="-1" href="https://code.google.com/p/dygraphs/issues/list">Issue Tracker</a></li>
-              <li role="presentation"><a role="menuitem" tabindex="-1" href="https://code.google.com/p/dygraphs/issues/entry">Report a Bug</a></li>
+              <li role="presentation"><a role="menuitem" tabindex="-1" href="https://github.com/danvk/dygraphs/issues">Issue Tracker</a></li>
+              <li role="presentation"><a role="menuitem" tabindex="-1" href="https://github.com/danvk/dygraphs/issues/new">Report a Bug</a></li>
             </ul>
           </li>
         </ul> <!-- /tabs -->
index 3e3d176..c223f04 100644 (file)
  - excanvas.js (Apache License)
  - YUI compressor (BSD License)
  - JsDoc Toolkit (MIT license)
- - stacktrace.js is public domain
+ - console-polyfill (MIT license)
 
 automated tests use:
  - auto_tests/lib/jquery-1.4.2.js (MIT &amp; GPL2)
  - auto_tests/lib/Asserts.js (Apache 2.0 License)
  - auto-tests/lib/JsTestDriver-1.3.3cjar (Apache 2.0 License
 
-Linter uses:
- - JSHint (modified MIT license; prevents evil)
-
 excanvas: http://code.google.com/p/explorercanvas/
 yui compressor: http://developer.yahoo.com/yui/compressor/
 jsdoc toolkit: http://code.google.com/p/jsdoc-toolkit/
@@ -27,8 +24,6 @@ jsdoc toolkit: http://code.google.com/p/jsdoc-toolkit/
 jquery: http://code.jquery.com/jquery-1.4.2.js
 Asserts.js: http://www.google.com/codesearch/p?#3tsINRJRCro/trunk/JsTestDriver/src/com/google/jstestdriver/javascript/Asserts.js
 JSTestDriver: http://code.google.com/p/js-test-driver/
-
-JSHint: jshint.com
 </pre>
 
 <!--#include virtual="footer.html" -->
index 6bf5fbc..4d7a7dd 100644 (file)
@@ -24,7 +24,7 @@
  * @constructor
  */
 
-/*jshint globalstrict: true */
+var DygraphCanvasRenderer = (function() {
 /*global Dygraph:false */
 "use strict";
 
@@ -52,10 +52,9 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
   this.layout = layout;
   this.element = element;
   this.elementContext = elementContext;
-  this.container = this.element.parentNode;
 
-  this.height = this.element.height;
-  this.width = this.element.width;
+  this.height = dygraph.height_;
+  this.width = dygraph.width_;
 
   // --- check whether everything is ok before we return
   // NOTE(konigsberg): isIE is never defined in this object. Bug of some sort.
@@ -64,8 +63,6 @@ var DygraphCanvasRenderer = function(dygraph, element, elementContext, layout) {
 
   // internal state
   this.area = layout.getPlotArea();
-  this.container.style.position = "relative";
-  this.container.style.width = this.width + "px";
 
   // Set up a clipping area for the canvas (and the interaction canvas).
   // This ensures that we don't overdraw.
@@ -188,7 +185,7 @@ DygraphCanvasRenderer.prototype._createIEClipArea = function() {
   // Right side
   createClipDiv({
     x: plotArea.x + plotArea.w, y: 0,
-    w: this.width-plotArea.x - plotArea.w,
+    w: this.width - plotArea.x - plotArea.w,
     h: this.height
   });
 
@@ -369,7 +366,7 @@ DygraphCanvasRenderer._drawPointsOnLine = function(
   for (var idx = 0; idx < pointsOnLine.length; idx++) {
     var cb = pointsOnLine[idx];
     ctx.save();
-    drawPointCallback(
+    drawPointCallback.call(e.dygraph,
         e.dygraph, e.setName, ctx, cb[0], cb[1], color, pointSize, cb[2]);
     ctx.restore();
   }
@@ -562,7 +559,7 @@ DygraphCanvasRenderer._errorPlotter = function(e) {
 
   var fillGraph = g.getBooleanOption("fillGraph", setName);
   if (fillGraph) {
-    Dygraph.warn("Can't use fillGraph option with error bars");
+    console.warn("Can't use fillGraph option with error bars");
   }
 
   var ctx = e.drawingContext;
@@ -629,6 +626,127 @@ DygraphCanvasRenderer._errorPlotter = function(e) {
   ctx.fill();
 };
 
+
+/**
+ * Proxy for CanvasRenderingContext2D which drops moveTo/lineTo calls which are
+ * superfluous. It accumulates all movements which haven't changed the x-value
+ * and only applies the two with the most extreme y-values.
+ * 
+ * Calls to lineTo/moveTo must have non-decreasing x-values.
+ */
+DygraphCanvasRenderer._fastCanvasProxy = function(context) {
+  var pendingActions = [];  // array of [type, x, y] tuples
+  var lastRoundedX = null;
+
+  var LINE_TO = 1,
+      MOVE_TO = 2;
+
+  var actionCount = 0;  // number of moveTos and lineTos passed to context.
+
+  // Drop superfluous motions
+  // Assumes all pendingActions have the same (rounded) x-value.
+  var compressActions = function(opt_losslessOnly) {
+    if (pendingActions.length <= 1) return;
+
+    // Lossless compression: drop inconsequential moveTos.
+    for (var i = pendingActions.length - 1; i > 0; i--) {
+      var action = pendingActions[i];
+      if (action[0] == MOVE_TO) {
+        var prevAction = pendingActions[i - 1];
+        if (prevAction[1] == action[1] && prevAction[2] == action[2]) {
+          pendingActions.splice(i, 1);
+        }
+      }
+    }
+
+    // Lossless compression: ... drop consecutive moveTos ...
+    for (var i = 0; i < pendingActions.length - 1; /* incremented internally */) {
+      var action = pendingActions[i];
+      if (action[0] == MOVE_TO && pendingActions[i + 1][0] == MOVE_TO) {
+        pendingActions.splice(i, 1);
+      } else {
+        i++;
+      }
+    }
+
+    // Lossy compression: ... drop all but the extreme y-values ...
+    if (pendingActions.length > 2 && !opt_losslessOnly) {
+      // keep an initial moveTo, but drop all others.
+      var startIdx = 0;
+      if (pendingActions[0][0] == MOVE_TO) startIdx++;
+      var minIdx = null, maxIdx = null;
+      for (var i = startIdx; i < pendingActions.length; i++) {
+        var action = pendingActions[i];
+        if (action[0] != LINE_TO) continue;
+        if (minIdx === null && maxIdx === null) {
+          minIdx = i;
+          maxIdx = i;
+        } else {
+          var y = action[2];
+          if (y < pendingActions[minIdx][2]) {
+            minIdx = i;
+          } else if (y > pendingActions[maxIdx][2]) {
+            maxIdx = i;
+          }
+        }
+      }
+      var minAction = pendingActions[minIdx],
+          maxAction = pendingActions[maxIdx];
+      pendingActions.splice(startIdx, pendingActions.length - startIdx);
+      if (minIdx < maxIdx) {
+        pendingActions.push(minAction);
+        pendingActions.push(maxAction);
+      } else if (minIdx > maxIdx) {
+        pendingActions.push(maxAction);
+        pendingActions.push(minAction);
+      } else {
+        pendingActions.push(minAction);
+      }
+    }
+  };
+
+  var flushActions = function(opt_noLossyCompression) {
+    compressActions(opt_noLossyCompression);
+    for (var i = 0, len = pendingActions.length; i < len; i++) {
+      var action = pendingActions[i];
+      if (action[0] == LINE_TO) {
+        context.lineTo(action[1], action[2]);
+      } else if (action[0] == MOVE_TO) {
+        context.moveTo(action[1], action[2]);
+      }
+    }
+    actionCount += pendingActions.length;
+    pendingActions = [];
+  };
+
+  var addAction = function(action, x, y) {
+    var rx = Math.round(x);
+    if (lastRoundedX === null || rx != lastRoundedX) {
+      flushActions();
+      lastRoundedX = rx;
+    }
+    pendingActions.push([action, x, y]);
+  };
+
+  return {
+    moveTo: function(x, y) {
+      addAction(MOVE_TO, x, y);
+    },
+    lineTo: function(x, y) {
+      addAction(LINE_TO, x, y);
+    },
+
+    // for major operations like stroke/fill, we skip compression to ensure
+    // that there are no artifacts at the right edge.
+    stroke:    function() { flushActions(true); context.stroke(); },
+    fill:      function() { flushActions(true); context.fill(); },
+    beginPath: function() { flushActions(true); context.beginPath(); },
+    closePath: function() { flushActions(true); context.closePath(); },
+
+    _count: function() { return actionCount; }
+  };
+};
+
 /**
  * Draws the shaded regions when "fillGraph" is set. Not to be confused with
  * error bars.
@@ -665,7 +783,6 @@ DygraphCanvasRenderer._fillPlotter = function(e) {
 
   if (!anySeriesFilled) return;
 
-  var ctx = e.drawingContext;
   var area = e.plotArea;
   var sets = e.allSeriesPoints;
   var setCount = sets.length;
@@ -685,11 +802,23 @@ DygraphCanvasRenderer._fillPlotter = function(e) {
   var currBaseline;
   var prevStepPlot;  // for different line drawing modes (line/step) per series
 
+  // Helper function to trace a line back along the baseline.
+  var traceBackPath = function(ctx, baselineX, baselineY, pathBack) {
+    ctx.lineTo(baselineX, baselineY);
+    if (stackedGraph) {
+      for (var i = pathBack.length - 1; i >= 0; i--) {
+        var pt = pathBack[i];
+        ctx.lineTo(pt[0], pt[1]);
+      }
+    }
+  };
+
   // process sets in reverse order (needed for stacked graphs)
   for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {
+    var ctx = e.drawingContext;
     var setName = setNames[setIdx];
     if (!g.getBooleanOption('fillGraph', setName)) continue;
-    
+
     var stepPlot = g.getBooleanOption('stepPlot', setName);
     var color = colors[setIdx];
     var axis = g.axisPropertiesForSeries(setName);
@@ -714,9 +843,28 @@ DygraphCanvasRenderer._fillPlotter = function(e) {
     ctx.fillStyle = err_color;
     ctx.beginPath();
     var last_x, is_first = true;
+
+    // If the point density is high enough, dropping segments on their way to
+    // the canvas justifies the overhead of doing so.
+    if (points.length > 2 * g.width_) {
+      ctx = DygraphCanvasRenderer._fastCanvasProxy(ctx);
+    }
+
+    // For filled charts, we draw points from left to right, then back along
+    // the x-axis to complete a shape for filling.
+    // For stacked plots, this "back path" is a more complex shape. This array
+    // stores the [x, y] values needed to trace that shape.
+    var pathBack = [];
+
+    // TODO(danvk): there are a lot of options at play in this loop.
+    //     The logic would be much clearer if some (e.g. stackGraph and
+    //     stepPlot) were split off into separate sub-plotters.
+    var point;
     while (iter.hasNext) {
-      var point = iter.next();
-      if (!Dygraph.isOK(point.y)) {
+      point = iter.next();
+      if (!Dygraph.isOK(point.y) && !stepPlot) {
+        traceBackPath(ctx, prevX, prevYs[1], pathBack);
+        pathBack = [];
         prevX = NaN;
         if (point.y_stacked !== null && !isNaN(point.y_stacked)) {
           baseline[point.canvasx] = area.h * point.y_stacked + area.y;
@@ -744,10 +892,10 @@ DygraphCanvasRenderer._fillPlotter = function(e) {
         }
         newYs = [ point.canvasy, lastY ];
 
-        if(stepPlot) {
+        if (stepPlot) {
           // Step plots must keep track of the top and bottom of
           // the baseline at each point.
-          if(prevYs[0] === -1) {
+          if (prevYs[0] === -1) {
             baseline[point.canvasx] = [ point.canvasy, axisY ];
           } else {
             baseline[point.canvasx] = [ point.canvasy, prevYs[0] ];
@@ -757,32 +905,47 @@ DygraphCanvasRenderer._fillPlotter = function(e) {
         }
 
       } else {
-        newYs = [ point.canvasy, axisY ];
+        if (isNaN(point.canvasy) && stepPlot) {
+          newYs = [ area.y + area.h, axisY ];
+        } else {
+          newYs = [ point.canvasy, axisY ];
+        }
       }
       if (!isNaN(prevX)) {
-        ctx.moveTo(prevX, prevYs[0]);
-        
         // Move to top fill point
         if (stepPlot) {
           ctx.lineTo(point.canvasx, prevYs[0]);
-        } else {
           ctx.lineTo(point.canvasx, newYs[0]);
-        }
-        // Move to bottom fill point
-        if (prevStepPlot && currBaseline) {
-          // Draw to the bottom of the baseline
-          ctx.lineTo(point.canvasx, currBaseline[1]);
         } else {
-          ctx.lineTo(point.canvasx, newYs[1]);
+          ctx.lineTo(point.canvasx, newYs[0]);
         }
 
-        ctx.lineTo(prevX, prevYs[1]);
-        ctx.closePath();
+        // Record the baseline for the reverse path.
+        if (stackedGraph) {
+          pathBack.push([prevX, prevYs[1]]);
+          if (prevStepPlot && currBaseline) {
+            // Draw to the bottom of the baseline
+            pathBack.push([point.canvasx, currBaseline[1]]);
+          } else {
+            pathBack.push([point.canvasx, newYs[1]]);
+          }
+        }
+      } else {
+        ctx.moveTo(point.canvasx, newYs[1]);
+        ctx.lineTo(point.canvasx, newYs[0]);
       }
       prevYs = newYs;
       prevX = point.canvasx;
     }
     prevStepPlot = stepPlot;
+    if (newYs && point) {
+      traceBackPath(ctx, point.canvasx, newYs[1], pathBack);
+      pathBack = [];
+    }
     ctx.fill();
   }
 };
+
+return DygraphCanvasRenderer;
+
+})();
index 6adfa51..26a96df 100644 (file)
@@ -16,7 +16,7 @@
   // This list needs to be kept in sync w/ the one in generate-combined.sh
   // and the one in jsTestDriver.conf.
   var source_files = [
-    "stacktrace.js",
+    "polyfills/console.js",
     "dashed-canvas.js",
     "dygraph-options.js",
     "dygraph-layout.js",
index b4b8e5d..9cf9568 100644 (file)
@@ -1,3 +1,9 @@
+/**
+ * @license
+ * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
 // TODO(danvk): move the Dygraph definitions out of here once I closure-ify dygraphs.js
 /**
  * @param {!HTMLDivElement|string} div
@@ -19,6 +25,15 @@ Dygraph.VERSION;
 /** @type {function(): string} */
 Dygraph.toString;
 
+/** @type {function(Event, Dygraph, DygraphInteractionContext)} */
+Dygraph.startPan;
+
+/** @type {function(Event, Dygraph, DygraphInteractionContext)} */
+Dygraph.movePan;
+
+/** @type {function(Event, Dygraph, DygraphInteractionContext)} */
+Dygraph.endPan;
+
 /** @type {function(?string): boolean} */
 Dygraph.prototype.isZoomed;
 
index 988e0ac..d2d7a0d 100644 (file)
@@ -17,7 +17,7 @@
  * - http://dygraphs.com/tests/annotation-gviz.html
  */
 
-/*jshint globalstrict: true */
+(function() {
 /*global Dygraph:false */
 "use strict";
 
@@ -80,3 +80,5 @@ Dygraph.GVizChart.prototype.getSelection = function() {
 
   return selection;
 };
+
+})();
index 75ee78f..53e2f44 100644 (file)
  * @author Robert Konigsberg (konigsberg@google.com)
  */
 
-/*jshint globalstrict: true */
+(function() {
 /*global Dygraph:false */
 "use strict";
 
 /**
+ * You can drag this many pixels past the edge of the chart and still have it
+ * be considered a zoom. This makes it easier to zoom to the exact edge of the
+ * chart, a fairly common operation.
+ */
+var DRAG_EDGE_MARGIN = 100;
+
+/**
  * A collection of functions to facilitate build custom interaction models.
  * @class
  */
 Dygraph.Interaction = {};
 
 /**
+ * Checks whether the beginning & ending of an event were close enough that it
+ * should be considered a click. If it should, dispatch appropriate events.
+ * Returns true if the event was treated as a click.
+ *
+ * @param {Event} event
+ * @param {Dygraph} g
+ * @param {Object} context
+ */
+Dygraph.Interaction.maybeTreatMouseOpAsClick = function(event, g, context) {
+  context.dragEndX = Dygraph.dragGetX_(event, context);
+  context.dragEndY = Dygraph.dragGetY_(event, context);
+  var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
+  var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
+
+  if (regionWidth < 2 && regionHeight < 2 &&
+      g.lastx_ !== undefined && g.lastx_ != -1) {
+    Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
+  }
+
+  context.regionWidth = regionWidth;
+  context.regionHeight = regionHeight;
+};
+
+/**
  * Called in response to an interaction model operation that
  * should start the default panning behavior.
  *
@@ -38,8 +69,14 @@ Dygraph.Interaction.startPan = function(event, g, context) {
   var i, axis;
   context.isPanning = true;
   var xRange = g.xAxisRange();
-  context.dateRange = xRange[1] - xRange[0];
-  context.initialLeftmostDate = xRange[0];
+
+  if (g.getOptionForAxis("logscale", "x")) {
+    context.initialLeftmostDate = Dygraph.log10(xRange[0]);
+    context.dateRange = Dygraph.log10(xRange[1]) - Dygraph.log10(xRange[0]);
+  } else {
+    context.initialLeftmostDate = xRange[0];    
+    context.dateRange = xRange[1] - xRange[0];
+  }
   context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);
 
   if (g.getNumericOption("panEdgeFraction")) {
@@ -132,7 +169,12 @@ Dygraph.Interaction.movePan = function(event, g, context) {
     }
   }
 
-  g.dateWindow_ = [minDate, maxDate];
+  if (g.getOptionForAxis("logscale", "x")) {
+    g.dateWindow_ = [ Math.pow(Dygraph.LOG_SCALE, minDate),
+                      Math.pow(Dygraph.LOG_SCALE, maxDate) ];
+  } else {
+    g.dateWindow_ = [minDate, maxDate];    
+  }
 
   // y-axis scaling is automatic unless this is a full 2D pan.
   if (context.is2DPan) {
@@ -160,8 +202,7 @@ Dygraph.Interaction.movePan = function(event, g, context) {
           minValue = maxValue - axis_data.dragValueRange;
         }
       }
-      var logscale = g.attributes_.getForAxis("logscale", i);
-      if (logscale) {
+      if (g.attributes_.getForAxis("logscale", i)) {
         axis.valueWindow = [ Math.pow(Dygraph.LOG_SCALE, minValue),
                              Math.pow(Dygraph.LOG_SCALE, maxValue) ];
       } else {
@@ -187,29 +228,7 @@ Dygraph.Interaction.movePan = function(event, g, context) {
  *     dragStartX/dragStartY/etc. properties). This function modifies the
  *     context.
  */
-Dygraph.Interaction.endPan = function(event, g, context) {
-  context.dragEndX = Dygraph.dragGetX_(event, context);
-  context.dragEndY = Dygraph.dragGetY_(event, context);
-
-  var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
-  var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
-
-  if (regionWidth < 2 && regionHeight < 2 &&
-      g.lastx_ !== undefined && g.lastx_ != -1) {
-    Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
-  }
-
-  // TODO(konigsberg): mouseup should just delete the
-  // context object, and mousedown should create a new one.
-  context.isPanning = false;
-  context.is2DPan = false;
-  context.initialLeftmostDate = null;
-  context.dateRange = null;
-  context.valueRange = null;
-  context.boundedDates = null;
-  context.boundedValues = null;
-  context.axes = null;
-};
+Dygraph.Interaction.endPan = Dygraph.Interaction.maybeTreatMouseOpAsClick;
 
 /**
  * Called in response to an interaction model operation that
@@ -271,6 +290,7 @@ Dygraph.Interaction.moveZoom = function(event, g, context) {
 };
 
 /**
+ * TODO(danvk): move this logic into dygraph.js
  * @param {Dygraph} g
  * @param {Event} event
  * @param {Object} context
@@ -281,38 +301,57 @@ Dygraph.Interaction.treatMouseOpAsClick = function(g, event, context) {
 
   var selectedPoint = null;
 
-  // Find out if the click occurs on a point. This only matters if there's a
-  // pointClickCallback.
-  if (pointClickCallback) {
-    var closestIdx = -1;
-    var closestDistance = Number.MAX_VALUE;
-
-    // check if the click was on a particular point.
-    for (var i = 0; i < g.selPoints_.length; i++) {
-      var p = g.selPoints_[i];
-      var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
-                     Math.pow(p.canvasy - context.dragEndY, 2);
-      if (!isNaN(distance) &&
-          (closestIdx == -1 || distance < closestDistance)) {
-        closestDistance = distance;
-        closestIdx = i;
-      }
+  // Find out if the click occurs on a point.
+  var closestIdx = -1;
+  var closestDistance = Number.MAX_VALUE;
+
+  // check if the click was on a particular point.
+  for (var i = 0; i < g.selPoints_.length; i++) {
+    var p = g.selPoints_[i];
+    var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
+                   Math.pow(p.canvasy - context.dragEndY, 2);
+    if (!isNaN(distance) &&
+        (closestIdx == -1 || distance < closestDistance)) {
+      closestDistance = distance;
+      closestIdx = i;
     }
+  }
 
-    // Allow any click within two pixels of the dot.
-    var radius = g.getNumericOption('highlightCircleSize') + 2;
-    if (closestDistance <= radius * radius) {
-      selectedPoint = g.selPoints_[closestIdx];
-    }
+  // Allow any click within two pixels of the dot.
+  var radius = g.getNumericOption('highlightCircleSize') + 2;
+  if (closestDistance <= radius * radius) {
+    selectedPoint = g.selPoints_[closestIdx];
   }
 
   if (selectedPoint) {
-    pointClickCallback(event, selectedPoint);
+    var e = {
+      cancelable: true,
+      point: selectedPoint,
+      canvasx: context.dragEndX,
+      canvasy: context.dragEndY
+    };
+    var defaultPrevented = g.cascadeEvents_('pointClick', e);
+    if (defaultPrevented) {
+      // Note: this also prevents click / clickCallback from firing.
+      return;
+    }
+    if (pointClickCallback) {
+      pointClickCallback.call(g, event, selectedPoint);
+    }
   }
 
-  // TODO(danvk): pass along more info about the points, e.g. 'x'
-  if (clickCallback) {
-    clickCallback(event, g.lastx_, g.selPoints_);
+  var e = {
+    cancelable: true,
+    xval: g.lastx_,  // closest point by x value
+    pts: g.selPoints_,
+    canvasx: context.dragEndX,
+    canvasy: context.dragEndY
+  };
+  if (!g.cascadeEvents_('click', e)) {
+    if (clickCallback) {
+      // TODO(danvk): pass along more info about the points, e.g. 'x'
+      clickCallback.call(g, event, g.lastx_, g.selPoints_);
+    }
   }
 };
 
@@ -332,22 +371,16 @@ Dygraph.Interaction.treatMouseOpAsClick = function(g, event, context) {
  *     context.
  */
 Dygraph.Interaction.endZoom = function(event, g, context) {
+  g.clearZoomRect_();
   context.isZooming = false;
-  context.dragEndX = Dygraph.dragGetX_(event, context);
-  context.dragEndY = Dygraph.dragGetY_(event, context);
-  var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
-  var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
-
-  if (regionWidth < 2 && regionHeight < 2 &&
-      g.lastx_ !== undefined && g.lastx_ != -1) {
-    Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
-  }
+  Dygraph.Interaction.maybeTreatMouseOpAsClick(event, g, context);
 
   // The zoom rectangle is visibly clipped to the plot area, so its behavior
   // should be as well.
   // See http://code.google.com/p/dygraphs/issues/detail?id=280
   var plotArea = g.getArea();
-  if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) {
+  if (context.regionWidth >= 10 &&
+      context.dragDirection == Dygraph.HORIZONTAL) {
     var left = Math.min(context.dragStartX, context.dragEndX),
         right = Math.max(context.dragStartX, context.dragEndX);
     left = Math.max(left, plotArea.x);
@@ -356,7 +389,8 @@ Dygraph.Interaction.endZoom = function(event, g, context) {
       g.doZoomX_(left, right);
     }
     context.cancelNextDblclick = true;
-  } else if (regionHeight >= 10 && context.dragDirection == Dygraph.VERTICAL) {
+  } else if (context.regionHeight >= 10 &&
+             context.dragDirection == Dygraph.VERTICAL) {
     var top = Math.min(context.dragStartY, context.dragEndY),
         bottom = Math.max(context.dragStartY, context.dragEndY);
     top = Math.max(top, plotArea.y);
@@ -365,8 +399,6 @@ Dygraph.Interaction.endZoom = function(event, g, context) {
       g.doZoomY_(top, bottom);
     }
     context.cancelNextDblclick = true;
-  } else {
-    if (context.zoomMoved) g.clearZoomRect_();
   }
   context.dragStartX = null;
   context.dragStartY = null;
@@ -525,7 +557,7 @@ Dygraph.Interaction.moveTouch = function(event, g, context) {
   // We only call zoomCallback on zooms, not pans, to mirror desktop behavior.
   if (didZoom && touches.length > 1 && g.getFunctionOption('zoomCallback')) {
     var viewWindow = g.xAxisRange();
-    g.getFunctionOption("zoomCallback")(viewWindow[0], viewWindow[1], g.yAxisRanges());
+    g.getFunctionOption("zoomCallback").call(g, viewWindow[0], viewWindow[1], g.yAxisRanges());
   }
 };
 
@@ -555,6 +587,40 @@ Dygraph.Interaction.endTouch = function(event, g, context) {
   }
 };
 
+// Determine the distance from x to [left, right].
+var distanceFromInterval = function(x, left, right) {
+  if (x < left) {
+    return left - x;
+  } else if (x > right) {
+    return x - right;
+  } else {
+    return 0;
+  }
+};
+
+/**
+ * Returns the number of pixels by which the event happens from the nearest
+ * edge of the chart. For events in the interior of the chart, this returns zero.
+ */
+var distanceFromChart = function(event, g) {
+  var chartPos = Dygraph.findPos(g.canvas_);
+  var box = {
+    left: chartPos.x,
+    right: chartPos.x + g.canvas_.offsetWidth,
+    top: chartPos.y,
+    bottom: chartPos.y + g.canvas_.offsetHeight
+  };
+
+  var pt = {
+    x: Dygraph.pageX(event),
+    y: Dygraph.pageY(event)
+  };
+
+  var dx = distanceFromInterval(pt.x, box.left, box.right),
+      dy = distanceFromInterval(pt.y, box.top, box.bottom);
+  return Math.max(dx, dy);
+};
+
 /**
  * Default interation model for dygraphs. You can refer to specific elements of
  * this when constructing your own interaction model, e.g.:
@@ -577,24 +643,47 @@ Dygraph.Interaction.defaultModel = {
     } else {
       Dygraph.startZoom(event, g, context);
     }
-  },
 
-  // Draw zoom rectangles when the mouse is down and the user moves around
-  mousemove: function(event, g, context) {
-    if (context.isZooming) {
-      Dygraph.moveZoom(event, g, context);
-    } else if (context.isPanning) {
-      Dygraph.movePan(event, g, context);
-    }
-  },
+    // Note: we register mousemove/mouseup on document to allow some leeway for
+    // events to move outside of the chart. Interaction model events get
+    // registered on the canvas, which is too small to allow this.
+    var mousemove = function(event) {
+      if (context.isZooming) {
+        // When the mouse moves >200px from the chart edge, cancel the zoom.
+        var d = distanceFromChart(event, g);
+        if (d < DRAG_EDGE_MARGIN) {
+          Dygraph.moveZoom(event, g, context);
+        } else {
+          if (context.dragEndX !== null) {
+            context.dragEndX = null;
+            context.dragEndY = null;
+            g.clearZoomRect_();
+          }
+        }
+      } else if (context.isPanning) {
+        Dygraph.movePan(event, g, context);
+      }
+    };
+    var mouseup = function(event) {
+      if (context.isZooming) {
+        if (context.dragEndX !== null) {
+          Dygraph.endZoom(event, g, context);
+        } else {
+          Dygraph.Interaction.maybeTreatMouseOpAsClick(event, g, context);
+        }
+      } else if (context.isPanning) {
+        Dygraph.endPan(event, g, context);
+      }
 
-  mouseup: function(event, g, context) {
-    if (context.isZooming) {
-      Dygraph.endZoom(event, g, context);
-    } else if (context.isPanning) {
-      Dygraph.endPan(event, g, context);
-    }
+      Dygraph.removeEvent(document, 'mousemove', mousemove);
+      Dygraph.removeEvent(document, 'mouseup', mouseup);
+      context.destroy();
+    };
+
+    g.addAndTrackEvent(document, 'mousemove', mousemove);
+    g.addAndTrackEvent(document, 'mouseup', mouseup);
   },
+  willDestroyContextMyself: true,
 
   touchstart: function(event, g, context) {
     Dygraph.Interaction.startTouch(event, g, context);
@@ -606,21 +695,22 @@ Dygraph.Interaction.defaultModel = {
     Dygraph.Interaction.endTouch(event, g, context);
   },
 
-  // Temporarily cancel the dragging event when the mouse leaves the graph
-  mouseout: function(event, g, context) {
-    if (context.isZooming) {
-      context.dragEndX = null;
-      context.dragEndY = null;
-      g.clearZoomRect_();
-    }
-  },
-
   // Disable zooming out if panning.
   dblclick: function(event, g, context) {
     if (context.cancelNextDblclick) {
       context.cancelNextDblclick = false;
       return;
     }
+
+    // Give plugins a chance to grab this event.
+    var e = {
+      canvasx: context.dragEndX,
+      canvasy: context.dragEndY
+    };
+    if (g.cascadeEvents_('dblclick', e)) {
+      return;
+    }
+
     if (event.altKey || event.shiftKey) {
       return;
     }
@@ -643,18 +733,7 @@ Dygraph.Interaction.nonInteractiveModel_ = {
   mousedown: function(event, g, context) {
     context.initializeMouseDown(event, g, context);
   },
-  mouseup: function(event, g, context) {
-    // TODO(danvk): this logic is repeated in Dygraph.Interaction.endZoom
-    context.dragEndX = Dygraph.dragGetX_(event, context);
-    context.dragEndY = Dygraph.dragGetY_(event, context);
-    var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
-    var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
-
-    if (regionWidth < 2 && regionHeight < 2 &&
-        g.lastx_ !== undefined && g.lastx_ != -1) {
-      Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
-    }
-  }
+  mouseup: Dygraph.Interaction.maybeTreatMouseOpAsClick
 };
 
 // Default interaction model when using the range selector.
@@ -674,3 +753,5 @@ Dygraph.Interaction.dragIsPanInteractionModel = {
     }
   }
 };
+
+})();
index 92e5cc3..470bdd7 100644 (file)
@@ -2,12 +2,6 @@
 // - declares symbols that are provided outisde of dygraphs (e.g. by excanvas)
 // - defines custom types used internally
 
-/**
- * @param {Object} dict
- * @return {!Array.<string>}
- */
-function printStackTrace(dict) {}
-
 
 /**
  * @constructor
@@ -22,7 +16,7 @@ G_vmlCanvasManager.initElement = function(canvas) {};
 // For IE
 /**
  * @param {string} type
- * @param {Object} fn
+ * @param {Function} fn
  */
 Element.prototype.detachEvent = function(type, fn) {};
 
index 4caa935..ef1df91 100644 (file)
@@ -9,7 +9,8 @@
  * dygraphs.
  */
 
-/*jshint globalstrict: true */
+var DygraphLayout = (function() {
+
 /*global Dygraph:false */
 "use strict";
 
@@ -149,13 +150,13 @@ DygraphLayout.prototype.setAnnotations = function(ann) {
   for (var i = 0; i < ann.length; i++) {
     var a = {};
     if (!ann[i].xval && ann[i].x === undefined) {
-      Dygraph.error("Annotations must have an 'x' property");
+      console.error("Annotations must have an 'x' property");
       return;
     }
     if (ann[i].icon &&
         !(ann[i].hasOwnProperty('width') &&
           ann[i].hasOwnProperty('height'))) {
-      Dygraph.error("Must set width and height when setting " +
+      console.error("Must set width and height when setting " +
                     "annotation.icon property");
       return;
     }
@@ -175,6 +176,7 @@ DygraphLayout.prototype.setYAxes = function (yAxes) {
 };
 
 DygraphLayout.prototype.evaluate = function() {
+  this._xAxis = {};
   this._evaluateLimits();
   this._evaluateLineCharts();
   this._evaluateLineTicks();
@@ -183,11 +185,15 @@ DygraphLayout.prototype.evaluate = function() {
 
 DygraphLayout.prototype._evaluateLimits = function() {
   var xlimits = this.dygraph_.xAxisRange();
-  this.minxval = xlimits[0];
-  this.maxxval = xlimits[1];
+  this._xAxis.minval = xlimits[0];
+  this._xAxis.maxval = xlimits[1];
   var xrange = xlimits[1] - xlimits[0];
-  this.xscale = (xrange !== 0 ? 1 / xrange : 1.0);
+  this._xAxis.scale = (xrange !== 0 ? 1 / xrange : 1.0);
 
+  if (this.dygraph_.getOptionForAxis("logscale", 'x')) {
+    this._xAxis.xlogrange = Dygraph.log10(this._xAxis.maxval) - Dygraph.log10(this._xAxis.minval);
+    this._xAxis.xlogscale = (this._xAxis.xlogrange !== 0 ? 1.0 / this._xAxis.xlogrange : 1.0);
+  }
   for (var i = 0; i < this.yAxes_.length; i++) {
     var axis = this.yAxes_[i];
     axis.minyval = axis.computedValueRange[0];
@@ -195,11 +201,11 @@ DygraphLayout.prototype._evaluateLimits = function() {
     axis.yrange = axis.maxyval - axis.minyval;
     axis.yscale = (axis.yrange !== 0 ? 1.0 / axis.yrange : 1.0);
 
-    if (axis.g.getOption("logscale")) {
+    if (this.dygraph_.getOption("logscale")) {
       axis.ylogrange = Dygraph.log10(axis.maxyval) - Dygraph.log10(axis.minyval);
       axis.ylogscale = (axis.ylogrange !== 0 ? 1.0 / axis.ylogrange : 1.0);
       if (!isFinite(axis.ylogrange) || isNaN(axis.ylogrange)) {
-        Dygraph.error('axis ' + i + ' of graph at ' + axis.g +
+        console.error('axis ' + i + ' of graph at ' + axis.g +
                       ' can\'t be displayed in log scale for range [' +
                       axis.minyval + ' - ' + axis.maxyval + ']');
       }
@@ -207,6 +213,14 @@ DygraphLayout.prototype._evaluateLimits = function() {
   }
 };
 
+DygraphLayout.calcXNormal_ = function(value, xAxis, logscale) {
+  if (logscale) {
+    return ((Dygraph.log10(value) - Dygraph.log10(xAxis.minval)) * xAxis.xlogscale);
+  } else {
+    return (value - xAxis.minval) * xAxis.scale;
+  }
+};
+
 /**
  * @param {DygraphAxisType} axis
  * @param {number} value
@@ -224,6 +238,7 @@ DygraphLayout.calcYNormal_ = function(axis, value, logscale) {
 
 DygraphLayout.prototype._evaluateLineCharts = function() {
   var isStacked = this.dygraph_.getOption("stackedGraph");
+  var isLogscaleForX = this.dygraph_.getOptionForAxis("logscale", 'x');
 
   for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
     var points = this.points[setIdx];
@@ -237,7 +252,7 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
       var point = points[j];
 
       // Range from 0-1 where 0 represents left and 1 represents right.
-      point.x = (point.xval - this.minxval) * this.xscale;
+      point.x = DygraphLayout.calcXNormal_(point.xval, this._xAxis, isLogscaleForX);
       // Range from 0-1 where 0 represents top and 1 represents bottom
       var yval = point.yval;
       if (isStacked) {
@@ -266,7 +281,7 @@ DygraphLayout.prototype._evaluateLineTicks = function() {
   for (i = 0; i < this.xTicks_.length; i++) {
     tick = this.xTicks_[i];
     label = tick.label;
-    pos = this.xscale * (tick.v - this.minxval);
+    pos = this.dygraph_.toPercentXCoord(tick.v);
     if ((pos >= 0.0) && (pos < 1.0)) {
       this.xticks.push([pos, label]);
     }
@@ -330,3 +345,7 @@ DygraphLayout.prototype.removeAllDatasets = function() {
   this.setPointsLengths = [];
   this.setPointsOffsets = [];
 };
+
+return DygraphLayout;
+
+})();
index acb0626..eb07d10 100644 (file)
@@ -4,9 +4,6 @@
  * MIT-licensed (http://opensource.org/licenses/MIT)
  */
 
-/*jshint globalstrict: true */
-/*global Dygraph:false */
-
 // NOTE: in addition to parsing as JS, this snippet is expected to be valid
 // JSON. This assumption cannot be checked in JS, but it will be checked when
 // documentation is generated by the generate-documentation.py script. For the
@@ -339,8 +336,8 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
   "axis": {
     "default": "(none)",
     "labels": ["Axis display"],
-    "type": "string or object",
-    "description": "Set to either an object ({}) filled with options for this axis or to the name of an existing data series with its own axis to re-use that axis. See tests for usage."
+    "type": "string",
+    "description": "Set to either 'y1' or 'y2' to assign a series to a y-axis (primary or secondary). Must be set per-series."
   },
   "pixelsPerXLabel": {
     "default": "",
@@ -371,7 +368,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "false",
     "labels": ["Axis display"],
     "type": "boolean",
-    "description": "When set for a y-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed.\n\nNot compatible with showZero, and ignores connectSeparatedPoints. Also, showing log scale with valueRanges that are less than zero will result in an unviewable graph."
+    "description": "When set for the y-axis or x-axis, the graph shows that axis in log scale. Any values less than or equal to zero are not displayed. Showing log scale with ranges that go below zero will result in an unviewable graph.\n\n Not compatible with showZero. connectSeparatedPoints is ignored. This is ignored for date-based x-axes."
   },
   "strokeWidth": {
     "default": "1.0",
@@ -425,6 +422,13 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "red, blue",
     "description": "The color of the gridlines. This may be set on a per-axis basis to define each axis' grid separately."
   },
+  "gridLinePattern": {
+    "default": "null",
+    "labels": ["Grid"],
+    "type": "array<integer>",
+    "example": "[10, 2, 5, 2]",
+    "description": "A custom pattern array where the even index is a draw and odd is a space in pixels. If null then it draws a solid line. The array should have a even length as any odd lengthed array could be expressed as a smaller even length array. This is used to create dashed gridlines."
+  },
   "visibility": {
     "default": "[true, true, ...]",
     "labels": ["Data Line display"],
@@ -486,6 +490,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "boolean",
     "description": "When set, display the graph as a step plot instead of a line plot. This option may either be set for the whole graph or for single series."
   },
+  "labelsUTC": {
+    "default": "false",
+    "labels": ["Value display/formatting", "Axis display"],
+    "type": "boolean",
+    "description": "Show date/time labels according to UTC (instead of local time)."
+  },
   "labelsKMB": {
     "default": "false",
     "labels": ["Value display/formatting"],
@@ -793,6 +803,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "string",
     "description": "The range selector mini plot fill color. This can be of the form \"#AABBCC\" or \"rgb(255,100,200)\" or \"yellow\". You can also specify null or \"\" to turn off fill."
   },
+  "showInRangeSelector": {
+    "default": "null",
+    "labels": ["Interactive Elements"],
+    "type": "boolean",
+    "description": "Mark this series for inclusion in the range selector. The mini plot curve will be an average of all such series. If this is not specified for any series, the default behavior is to average all the series. Setting it for one series will result in that series being charted alone in the range selector."
+  },
   "animatedZooms": {
     "default": "false",
     "labels": ["Interactive Elements"],
@@ -805,6 +821,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "type": "array or function",
     "description": "A function (or array of functions) which plot each data series on the chart. TODO(danvk): more details! May be set per-series."
   },
+  "axes": {
+    "default": "null",
+    "labels": ["Configuration"],
+    "type": "Object",
+    "description": "Defines per-axis options. Valid keys are 'x', 'y' and 'y2'. Only some options may be set on a per-axis basis. If an option may be set in this way, it will be noted on this page. See also documentation on <a href='http://dygraphs.com/per-axis.html'>per-series and per-axis options</a>."
+  },
   "series": {
     "default": "null",
     "labels": ["Series"],
index 2dc88ae..fd6876a 100644 (file)
  */
 
 var DygraphOptions = (function() {
+/*jshint strict:false */
 
+// For "production" code, this gets set to false by uglifyjs.
+// Need to define it outside of "use strict", hence the nested IIFEs.
+if (typeof(DEBUG) === 'undefined') DEBUG=true;
+
+return (function() {
+
+// TODO: remove this jshint directive & fix the warnings.
 /*jshint sub:true */
 /*global Dygraph:false */
 "use strict";
@@ -183,7 +191,7 @@ DygraphOptions.prototype.reparseSeries = function() {
 
       if (typeof(axis) == 'string') {
         if (!this.series_.hasOwnProperty(axis)) {
-          Dygraph.error("Series " + seriesName + " wants to share a y-axis with " +
+          console.error("Series " + seriesName + " wants to share a y-axis with " +
                      "series " + axis + ", which does not define its own axis.");
           return;
         }
@@ -217,6 +225,8 @@ DygraphOptions.prototype.reparseSeries = function() {
     Dygraph.update(this.yAxes_[1].options, axis_opts["y2"] || {});
   }
   Dygraph.update(this.xAxis_.options, axis_opts["x"] || {});
+
+  if (DEBUG) this.validateOptions_();
 };
 
 /**
@@ -290,11 +300,13 @@ DygraphOptions.prototype.getForAxis = function(name, axis) {
   }
 
   // User-specified global options second.
-  var result = this.getGlobalUser_(name);
-  if (result !== null) {
-    return result;
+  // But, hack, ignore globally-specified 'logscale' for 'x' axis declaration.
+  if (!(axis === 'x' && name === 'logscale')) {
+    var result = this.getGlobalUser_(name);
+    if (result !== null) {
+      return result;
+    }
   }
-
   // Default axis options third.
   var defaultAxisOptions = Dygraph.DEFAULT_ATTRS.axes[axisString];
   if (defaultAxisOptions.hasOwnProperty(name)) {
@@ -370,6 +382,77 @@ DygraphOptions.prototype.seriesNames = function() {
   return this.labels_;
 };
 
+if (DEBUG) {
+
+/**
+ * Validate all options.
+ * This requires Dygraph.OPTIONS_REFERENCE, which is only available in debug builds.
+ * @private
+ */
+DygraphOptions.prototype.validateOptions_ = function() {
+  if (typeof Dygraph.OPTIONS_REFERENCE === 'undefined') {
+    throw 'Called validateOptions_ in prod build.';
+  }
+
+  var that = this;
+  var validateOption = function(optionName) {
+    if (!Dygraph.OPTIONS_REFERENCE[optionName]) {
+      that.warnInvalidOption_(optionName);
+    }
+  };
+
+  var optionsDicts = [this.xAxis_.options,
+                      this.yAxes_[0].options,
+                      this.yAxes_[1] && this.yAxes_[1].options,
+                      this.global_,
+                      this.user_,
+                      this.highlightSeries_];
+  var names = this.seriesNames();
+  for (var i = 0; i < names.length; i++) {
+    var name = names[i];
+    if (this.series_.hasOwnProperty(name)) {
+      optionsDicts.push(this.series_[name].options);
+    }
+  }
+  for (var i = 0; i < optionsDicts.length; i++) {
+    var dict = optionsDicts[i];
+    if (!dict) continue;
+    for (var optionName in dict) {
+      if (dict.hasOwnProperty(optionName)) {
+        validateOption(optionName);
+      }
+    }
+  }
+};
+
+var WARNINGS = {};  // Only show any particular warning once.
+
+/**
+ * Logs a warning about invalid options.
+ * TODO: make this throw for testing
+ * @private
+ */
+DygraphOptions.prototype.warnInvalidOption_ = function(optionName) {
+  if (!WARNINGS[optionName]) {
+    WARNINGS[optionName] = true;
+    var isSeries = (this.labels_.indexOf(optionName) >= 0);
+    if (isSeries) {
+      console.warn('Use new-style per-series options (saw ' + optionName + ' as top-level options key). See http://bit.ly/1tceaJs');
+    } else {
+      console.warn('Unknown option ' + optionName + ' (full list of options at dygraphs.com/options.html');
+      throw "invalid option " + optionName;
+    }
+  }
+};
+
+// Reset list of previously-shown warnings. Used for testing.
+DygraphOptions.resetWarnings_ = function() {
+  WARNINGS = {};
+};
+
+}
+
 return DygraphOptions;
 
 })();
+})();
index a3fe90a..654877b 100644 (file)
@@ -58,8 +58,9 @@
  *   middle of the years.
  */
 
-/*jshint globalstrict:true, sub:true */
+/*jshint sub:true */
 /*global Dygraph:false */
+(function() {
 "use strict";
 
 /** @typedef {Array.<{v:number, label:string, label_v:(string|undefined)}>} */
@@ -241,56 +242,42 @@ Dygraph.DECADAL = 19;
 Dygraph.CENTENNIAL = 20;
 Dygraph.NUM_GRANULARITIES = 21;
 
-/** @type {Array.<number>} */
-Dygraph.SHORT_SPACINGS = [];
-Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]        = 1000 * 1;
-Dygraph.SHORT_SPACINGS[Dygraph.TWO_SECONDLY]    = 1000 * 2;
-Dygraph.SHORT_SPACINGS[Dygraph.FIVE_SECONDLY]   = 1000 * 5;
-Dygraph.SHORT_SPACINGS[Dygraph.TEN_SECONDLY]    = 1000 * 10;
-Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_SECONDLY] = 1000 * 30;
-Dygraph.SHORT_SPACINGS[Dygraph.MINUTELY]        = 1000 * 60;
-Dygraph.SHORT_SPACINGS[Dygraph.TWO_MINUTELY]    = 1000 * 60 * 2;
-Dygraph.SHORT_SPACINGS[Dygraph.FIVE_MINUTELY]   = 1000 * 60 * 5;
-Dygraph.SHORT_SPACINGS[Dygraph.TEN_MINUTELY]    = 1000 * 60 * 10;
-Dygraph.SHORT_SPACINGS[Dygraph.THIRTY_MINUTELY] = 1000 * 60 * 30;
-Dygraph.SHORT_SPACINGS[Dygraph.HOURLY]          = 1000 * 3600;
-Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]      = 1000 * 3600 * 2;
-Dygraph.SHORT_SPACINGS[Dygraph.SIX_HOURLY]      = 1000 * 3600 * 6;
-Dygraph.SHORT_SPACINGS[Dygraph.DAILY]           = 1000 * 86400;
-Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]          = 1000 * 604800;
-
-/** 
- * A collection of objects specifying where it is acceptable to place tick
- * marks for granularities larger than WEEKLY.  
- * 'months' is an array of month indexes on which to place tick marks.
- * 'year_mod' ticks are placed when year % year_mod = 0.
- * @type {Array.<Object>} 
- */
-Dygraph.LONG_TICK_PLACEMENTS = [];
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.MONTHLY] = {
-  months : [0,1,2,3,4,5,6,7,8,9,10,11], 
-  year_mod : 1
-};
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.QUARTERLY] = {
-  months: [0,3,6,9], 
-  year_mod: 1
-};
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.BIANNUAL] = {
-  months: [0,6], 
-  year_mod: 1
-};
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.ANNUAL] = {
-  months: [0], 
-  year_mod: 1
-};
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.DECADAL] = {
-  months: [0], 
-  year_mod: 10
-};
-Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL] = {
-  months: [0], 
-  year_mod: 100
-};
+// Date components enumeration (in the order of the arguments in Date)
+// TODO: make this an @enum
+Dygraph.DATEFIELD_Y = 0;
+Dygraph.DATEFIELD_M = 1;
+Dygraph.DATEFIELD_D = 2;
+Dygraph.DATEFIELD_HH = 3;
+Dygraph.DATEFIELD_MM = 4;
+Dygraph.DATEFIELD_SS = 5;
+Dygraph.DATEFIELD_MS = 6;
+Dygraph.NUM_DATEFIELDS = 7;
+
+
+/** @type {Array.<{datefield:number, step:number, spacing:number}>} */
+Dygraph.TICK_PLACEMENT = [];
+Dygraph.TICK_PLACEMENT[Dygraph.SECONDLY]        = {datefield: Dygraph.DATEFIELD_SS, step:   1, spacing: 1000 * 1};
+Dygraph.TICK_PLACEMENT[Dygraph.TWO_SECONDLY]    = {datefield: Dygraph.DATEFIELD_SS, step:   2, spacing: 1000 * 2};
+Dygraph.TICK_PLACEMENT[Dygraph.FIVE_SECONDLY]   = {datefield: Dygraph.DATEFIELD_SS, step:   5, spacing: 1000 * 5};
+Dygraph.TICK_PLACEMENT[Dygraph.TEN_SECONDLY]    = {datefield: Dygraph.DATEFIELD_SS, step:  10, spacing: 1000 * 10};
+Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_SECONDLY] = {datefield: Dygraph.DATEFIELD_SS, step:  30, spacing: 1000 * 30};
+Dygraph.TICK_PLACEMENT[Dygraph.MINUTELY]        = {datefield: Dygraph.DATEFIELD_MM, step:   1, spacing: 1000 * 60};
+Dygraph.TICK_PLACEMENT[Dygraph.TWO_MINUTELY]    = {datefield: Dygraph.DATEFIELD_MM, step:   2, spacing: 1000 * 60 * 2};
+Dygraph.TICK_PLACEMENT[Dygraph.FIVE_MINUTELY]   = {datefield: Dygraph.DATEFIELD_MM, step:   5, spacing: 1000 * 60 * 5};
+Dygraph.TICK_PLACEMENT[Dygraph.TEN_MINUTELY]    = {datefield: Dygraph.DATEFIELD_MM, step:  10, spacing: 1000 * 60 * 10};
+Dygraph.TICK_PLACEMENT[Dygraph.THIRTY_MINUTELY] = {datefield: Dygraph.DATEFIELD_MM, step:  30, spacing: 1000 * 60 * 30};
+Dygraph.TICK_PLACEMENT[Dygraph.HOURLY]          = {datefield: Dygraph.DATEFIELD_HH, step:   1, spacing: 1000 * 3600};
+Dygraph.TICK_PLACEMENT[Dygraph.TWO_HOURLY]      = {datefield: Dygraph.DATEFIELD_HH, step:   2, spacing: 1000 * 3600 * 2};
+Dygraph.TICK_PLACEMENT[Dygraph.SIX_HOURLY]      = {datefield: Dygraph.DATEFIELD_HH, step:   6, spacing: 1000 * 3600 * 6};
+Dygraph.TICK_PLACEMENT[Dygraph.DAILY]           = {datefield: Dygraph.DATEFIELD_D,  step:   1, spacing: 1000 * 86400};
+Dygraph.TICK_PLACEMENT[Dygraph.WEEKLY]          = {datefield: Dygraph.DATEFIELD_D,  step:   7, spacing: 1000 * 604800};
+Dygraph.TICK_PLACEMENT[Dygraph.MONTHLY]         = {datefield: Dygraph.DATEFIELD_M,  step:   1, spacing: 1000 * 7200  * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 12
+Dygraph.TICK_PLACEMENT[Dygraph.QUARTERLY]       = {datefield: Dygraph.DATEFIELD_M,  step:   3, spacing: 1000 * 21600 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 4
+Dygraph.TICK_PLACEMENT[Dygraph.BIANNUAL]        = {datefield: Dygraph.DATEFIELD_M,  step:   6, spacing: 1000 * 43200 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 / 2
+Dygraph.TICK_PLACEMENT[Dygraph.ANNUAL]          = {datefield: Dygraph.DATEFIELD_Y,  step:   1, spacing: 1000 * 86400   * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 * 1
+Dygraph.TICK_PLACEMENT[Dygraph.DECADAL]         = {datefield: Dygraph.DATEFIELD_Y,  step:  10, spacing: 1000 * 864000  * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 * 10
+Dygraph.TICK_PLACEMENT[Dygraph.CENTENNIAL]      = {datefield: Dygraph.DATEFIELD_Y,  step: 100, spacing: 1000 * 8640000 * 365.2524}; // 1e3 * 60 * 60 * 24 * 365.2524 * 100
+
 
 /**
  * This is a list of human-friendly values at which to show tick marks on a log
@@ -299,7 +286,7 @@ Dygraph.LONG_TICK_PLACEMENTS[Dygraph.CENTENNIAL] = {
  * NOTE: this assumes that Dygraph.LOG_SCALE = 10.
  * @type {Array.<number>}
  */
-Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
+Dygraph.PREFERRED_LOG_TICK_VALUES = (function() {
   var vals = [];
   for (var power = -39; power <= 39; power++) {
     var range = Math.pow(10, power);
@@ -309,7 +296,7 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
     }
   }
   return vals;
-}();
+})();
 
 /**
  * Determine the correct granularity of ticks on a date axis.
@@ -317,8 +304,7 @@ Dygraph.PREFERRED_LOG_TICK_VALUES = function() {
  * @param {number} a Left edge of the chart (ms)
  * @param {number} b Right edge of the chart (ms)
  * @param {number} pixels Size of the chart in the relevant dimension (width).
- * @param {function(string):*} opts Function mapping from option name ->
- *     value.
+ * @param {function(string):*} opts Function mapping from option name -&gt; value.
  * @return {number} The appropriate axis granularity for this chart. See the
  *     enumeration of possible values in dygraph-tickers.js.
  */
@@ -334,26 +320,19 @@ Dygraph.pickDateTickGranularity = function(a, b, pixels, opts) {
 };
 
 /**
+ * Compute the number of ticks on a date axis for a given granularity.
  * @param {number} start_time
  * @param {number} end_time
  * @param {number} granularity (one of the granularities enumerated above)
- * @return {number} Number of ticks that would result.
+ * @return {number} (Approximate) number of ticks that would result.
  */
 Dygraph.numDateTicks = function(start_time, end_time, granularity) {
-  if (granularity < Dygraph.MONTHLY) {
-    // Generate one tick mark for every fixed interval of time.
-    var spacing = Dygraph.SHORT_SPACINGS[granularity];
-    return Math.floor(0.5 + 1.0 * (end_time - start_time) / spacing);
-  } else {
-    var tickPlacement = Dygraph.LONG_TICK_PLACEMENTS[granularity];
-
-    var msInYear = 365.2524 * 24 * 3600 * 1000;
-    var num_years = 1.0 * (end_time - start_time) / msInYear;
-    return Math.floor(0.5 + 1.0 * num_years * tickPlacement.months.length / tickPlacement.year_mod);
-  }
+  var spacing = Dygraph.TICK_PLACEMENT[granularity].spacing;
+  return Math.round(1.0 * (end_time - start_time) / spacing);
 };
 
 /**
+ * Compute the positions and labels of ticks on a date axis for a given granularity.
  * @param {number} start_time
  * @param {number} end_time
  * @param {number} granularity (one of the granularities enumerated above)
@@ -364,112 +343,86 @@ Dygraph.numDateTicks = function(start_time, end_time, granularity) {
 Dygraph.getDateAxis = function(start_time, end_time, granularity, opts, dg) {
   var formatter = /** @type{AxisLabelFormatter} */(
       opts("axisLabelFormatter"));
+  var utc = opts("labelsUTC");
+  var accessors = utc ? Dygraph.DateAccessorsUTC : Dygraph.DateAccessorsLocal;
+
+  var datefield = Dygraph.TICK_PLACEMENT[granularity].datefield;
+  var step = Dygraph.TICK_PLACEMENT[granularity].step;
+  var spacing = Dygraph.TICK_PLACEMENT[granularity].spacing;
+
+  // Choose a nice tick position before the initial instant.
+  // Currently, this code deals properly with the existent daily granularities:
+  // DAILY (with step of 1) and WEEKLY (with step of 7 but specially handled).
+  // Other daily granularities (say TWO_DAILY) should also be handled specially
+  // by setting the start_date_offset to 0.
+  var start_date = new Date(start_time);
+  var date_array = [];
+  date_array[Dygraph.DATEFIELD_Y]  = accessors.getFullYear(start_date);
+  date_array[Dygraph.DATEFIELD_M]  = accessors.getMonth(start_date);
+  date_array[Dygraph.DATEFIELD_D]  = accessors.getDate(start_date);
+  date_array[Dygraph.DATEFIELD_HH] = accessors.getHours(start_date);
+  date_array[Dygraph.DATEFIELD_MM] = accessors.getMinutes(start_date);
+  date_array[Dygraph.DATEFIELD_SS] = accessors.getSeconds(start_date);
+  date_array[Dygraph.DATEFIELD_MS] = accessors.getMilliseconds(start_date);
+
+  var start_date_offset = date_array[datefield] % step;
+  if (granularity == Dygraph.WEEKLY) {
+    // This will put the ticks on Sundays.
+    start_date_offset = accessors.getDay(start_date);
+  }
+  
+  date_array[datefield] -= start_date_offset;
+  for (var df = datefield + 1; df < Dygraph.NUM_DATEFIELDS; df++) {
+    // The minimum value is 1 for the day of month, and 0 for all other fields.
+    date_array[df] = (df === Dygraph.DATEFIELD_D) ? 1 : 0;
+  }
+
+  // Generate the ticks.
+  // For granularities not coarser than HOURLY we use the fact that:
+  //   the number of milliseconds between ticks is constant
+  //   and equal to the defined spacing.
+  // Otherwise we rely on the 'roll over' property of the Date functions:
+  //   when some date field is set to a value outside of its logical range,
+  //   the excess 'rolls over' the next (more significant) field.
+  // However, when using local time with DST transitions,
+  // there are dates that do not represent any time value at all
+  // (those in the hour skipped at the 'spring forward'),
+  // and the JavaScript engines usually return an equivalent value.
+  // Hence we have to check that the date is properly increased at each step,
+  // returning a date at a nice tick position.
   var ticks = [];
-  var t;
-
-  if (granularity < Dygraph.MONTHLY) {
-    // Generate one tick mark for every fixed interval of time.
-    var spacing = Dygraph.SHORT_SPACINGS[granularity];
-
-    // Find a time less than start_time which occurs on a "nice" time boundary
-    // for this granularity.
-    var g = spacing / 1000;
-    var d = new Date(start_time);
-    Dygraph.setDateSameTZ(d, {ms: 0});
-
-    var x;
-    if (g <= 60) {  // seconds
-      x = d.getSeconds();
-      Dygraph.setDateSameTZ(d, {s: x - x % g});
-    } else {
-      Dygraph.setDateSameTZ(d, {s: 0});
-      g /= 60;
-      if (g <= 60) {  // minutes
-        x = d.getMinutes();
-        Dygraph.setDateSameTZ(d, {m: x - x % g});
-      } else {
-        Dygraph.setDateSameTZ(d, {m: 0});
-        g /= 60;
-
-        if (g <= 24) {  // days
-          x = d.getHours();
-          d.setHours(x - x % g);
-        } else {
-          d.setHours(0);
-          g /= 24;
-
-          if (g == 7) {  // one week
-            d.setDate(d.getDate() - d.getDay());
-          }
-        }
-      }
+  var tick_date = accessors.makeDate.apply(null, date_array);
+  var tick_time = tick_date.getTime();
+  if (granularity <= Dygraph.HOURLY) {
+    if (tick_time < start_time) {
+      tick_time += spacing;
+      tick_date = new Date(tick_time);
     }
-    start_time = d.getTime();
-
-    // For spacings coarser than two-hourly, we want to ignore daylight
-    // savings transitions to get consistent ticks. For finer-grained ticks,
-    // it's essential to show the DST transition in all its messiness.
-    var start_offset_min = new Date(start_time).getTimezoneOffset();
-    var check_dst = (spacing >= Dygraph.SHORT_SPACINGS[Dygraph.TWO_HOURLY]);
-
-    for (t = start_time; t <= end_time; t += spacing) {
-      d = new Date(t);
-
-      // This ensures that we stay on the same hourly "rhythm" across
-      // daylight savings transitions. Without this, the ticks could get off
-      // by an hour. See tests/daylight-savings.html or issue 147.
-      if (check_dst && d.getTimezoneOffset() != start_offset_min) {
-        var delta_min = d.getTimezoneOffset() - start_offset_min;
-        t += delta_min * 60 * 1000;
-        d = new Date(t);
-        start_offset_min = d.getTimezoneOffset();
-
-        // Check whether we've backed into the previous timezone again.
-        // This can happen during a "spring forward" transition. In this case,
-        // it's best to skip this tick altogether (we may be shooting for a
-        // non-existent time like the 2AM that's skipped) and go to the next
-        // one.
-        if (new Date(t + spacing).getTimezoneOffset() != start_offset_min) {
-          t += spacing;
-          d = new Date(t);
-          start_offset_min = d.getTimezoneOffset();
-        }
-      }
-
-      ticks.push({ v:t,
-                   label: formatter(d, granularity, opts, dg)
+    while (tick_time <= end_time) {
+      ticks.push({ v: tick_time,
+                   label: formatter(tick_date, granularity, opts, dg)
                  });
+      tick_time += spacing;
+      tick_date = new Date(tick_time);
     }
   } else {
-    // Display a tick mark on the first of a set of months of each year.
-    // Years get a tick mark iff y % year_mod == 0. This is useful for
-    // displaying a tick mark once every 10 years, say, on long time scales.
-    var months;
-    var year_mod = 1;  // e.g. to only print one point every 10 years.
-
-    if (granularity < Dygraph.NUM_GRANULARITIES) {
-      months = Dygraph.LONG_TICK_PLACEMENTS[granularity].months;
-      year_mod = Dygraph.LONG_TICK_PLACEMENTS[granularity].year_mod;
-    } else {
-      Dygraph.warn("Span of dates is too long");
+    if (tick_time < start_time) {
+      date_array[datefield] += step;
+      tick_date = accessors.makeDate.apply(null, date_array);
+      tick_time = tick_date.getTime();
     }
-
-    var start_year = new Date(start_time).getFullYear();
-    var end_year   = new Date(end_time).getFullYear();
-    for (var i = start_year; i <= end_year; i++) {
-      if (i % year_mod !== 0) continue;
-      for (var j = 0; j < months.length; j++) {
-        var dt = new Date(i, months[j], 1);
-        dt.setFullYear(i);
-        t = dt.getTime();
-        if (t < start_time || t > end_time) continue;
-        ticks.push({ v:t,
-                     label: formatter(new Date(t), granularity, opts, dg)
+    while (tick_time <= end_time) {
+      if (granularity >= Dygraph.DAILY ||
+          accessors.getHours(tick_date) % step === 0) {
+        ticks.push({ v: tick_time,
+                     label: formatter(tick_date, granularity, opts, dg)
                    });
       }
+      date_array[datefield] += step;
+      tick_date = accessors.makeDate.apply(null, date_array);
+      tick_time = tick_date.getTime();
     }
   }
-
   return ticks;
 };
 
@@ -485,3 +438,5 @@ if (Dygraph &&
   Dygraph.DEFAULT_ATTRS['axes']['y']['ticker'] = Dygraph.numericTicks;
   Dygraph.DEFAULT_ATTRS['axes']['y2']['ticker'] = Dygraph.numericTicks;
 }
+
+})();
index 4e90693..4434758 100644 (file)
@@ -1,3 +1,9 @@
+/**
+ * @license
+ * Copyright 2006 Dan Vanderkam (danvdk@gmail.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
 // This file contains typedefs and externs that are needed by the Closure Compiler.
 
 /**
  */
 var DygraphInteractionContext;
 
+/**
+ * Point structure.
+ *
+ * xval_* and yval_* are the original unscaled data values,
+ * while x_* and y_* are scaled to the range (0.0-1.0) for plotting.
+ * yval_stacked is the cumulative Y value used for stacking graphs,
+ * and bottom/top/minus/plus are used for error bar graphs.
+ *
+ * @typedef {{
+ *     idx: number,
+ *     name: string,
+ *     x: ?number,
+ *     xval: ?number,
+ *     y_bottom: ?number,
+ *     y: ?number,
+ *     y_stacked: ?number,
+ *     y_top: ?number,
+ *     yval_minus: ?number,
+ *     yval: ?number,
+ *     yval_plus: ?number,
+ *     yval_stacked
+ * }}
+ */
+Dygraph.PointType;
index 63fc1e0..bf93665 100644 (file)
@@ -11,8 +11,9 @@
  * search) and generic DOM-manipulation functions.
  */
 
-/*jshint globalstrict: true */
-/*global Dygraph:false, G_vmlCanvasManager:false, Node:false, printStackTrace: false */
+(function() {
+
+/*global Dygraph:false, G_vmlCanvasManager:false, Node:false */
 "use strict";
 
 Dygraph.LOG_SCALE = 10;
@@ -27,20 +28,6 @@ Dygraph.log10 = function(x) {
   return Math.log(x) / Dygraph.LN_TEN;
 };
 
-// Various logging levels.
-Dygraph.DEBUG = 1;
-Dygraph.INFO = 2;
-Dygraph.WARNING = 3;
-Dygraph.ERROR = 3;
-
-// <REMOVE_FOR_COMBINED>
-// Set this to log stack traces on warnings, etc.
-// This requires stacktrace.js, which is up to you to provide.
-// A copy can be found in the dygraphs repo, or at
-// https://github.com/eriwen/javascript-stacktrace
-Dygraph.LOG_STACK_TRACES = false;
-// </REMOVE_FOR_COMBINED>
-
 /** A dotted line stroke pattern. */
 Dygraph.DOTTED_LINE = [2, 2];
 /** A dashed line stroke pattern. */
@@ -49,94 +36,6 @@ Dygraph.DASHED_LINE = [7, 3];
 Dygraph.DOT_DASH_LINE = [7, 2, 2, 2];
 
 /**
- * Log an error on the JS console at the given severity.
- * @param {number} severity One of Dygraph.{DEBUG,INFO,WARNING,ERROR}
- * @param {string} message The message to log.
- * @private
- */
-Dygraph.log = function(severity, message) {
-  // <REMOVE_FOR_COMBINED>
-  var st;
-  if (typeof(printStackTrace) != 'undefined') {
-    try {
-      // Remove uninteresting bits: logging functions and paths.
-      st = printStackTrace({guess:false});
-      while (st[0].indexOf("stacktrace") != -1) {
-        st.splice(0, 1);
-      }
-
-      st.splice(0, 2);
-      for (var i = 0; i < st.length; i++) {
-        st[i] = st[i].replace(/\([^)]*\/(.*)\)/, '@$1')
-            .replace(/\@.*\/([^\/]*)/, '@$1')
-            .replace('[object Object].', '');
-      }
-      var top_msg = st.splice(0, 1)[0];
-      message += ' (' + top_msg.replace(/^.*@ ?/, '') + ')';
-    } catch(e) {
-      // Oh well, it was worth a shot!
-    }
-  }
-  // </REMOVE_FOR_COMBINED>
-
-  if (typeof(window.console) != 'undefined') {
-    // In older versions of Firefox, only console.log is defined.
-    var console = window.console;
-    var log = function(console, method, msg) {
-      if (method && typeof(method) == 'function') {
-        method.call(console, msg);
-      } else {
-        console.log(msg);
-      }
-    };
-
-    switch (severity) {
-      case Dygraph.DEBUG:
-        log(console, console.debug, 'dygraphs: ' + message);
-        break;
-      case Dygraph.INFO:
-        log(console, console.info, 'dygraphs: ' + message);
-        break;
-      case Dygraph.WARNING:
-        log(console, console.warn, 'dygraphs: ' + message);
-        break;
-      case Dygraph.ERROR:
-        log(console, console.error, 'dygraphs: ' + message);
-        break;
-    }
-  }
-
-  // <REMOVE_FOR_COMBINED>
-  if (Dygraph.LOG_STACK_TRACES) {
-    window.console.log(st.join('\n'));
-  }
-  // </REMOVE_FOR_COMBINED>
-};
-
-/**
- * @param {string} message
- * @private
- */
-Dygraph.info = function(message) {
-  Dygraph.log(Dygraph.INFO, message);
-};
-
-/**
- * @param {string} message
- * @private
- */
-Dygraph.warn = function(message) {
-  Dygraph.log(Dygraph.WARNING, message);
-};
-
-/**
- * @param {string} message
- */
-Dygraph.error = function(message) {
-  Dygraph.log(Dygraph.ERROR, message);
-};
-
-/**
  * Return the 2d context for a dygraph canvas.
  *
  * This method is only exposed for the sake of replacing the function in
@@ -476,46 +375,90 @@ Dygraph.zeropad = function(x) {
 };
 
 /**
+ * Date accessors to get the parts of a calendar date (year, month, 
+ * day, hour, minute, second and millisecond) according to local time,
+ * and factory method to call the Date constructor with an array of arguments.
+ */
+Dygraph.DateAccessorsLocal = {
+  getFullYear:     function(d) {return d.getFullYear();},
+  getMonth:        function(d) {return d.getMonth();},
+  getDate:         function(d) {return d.getDate();},
+  getHours:        function(d) {return d.getHours();},
+  getMinutes:      function(d) {return d.getMinutes();},
+  getSeconds:      function(d) {return d.getSeconds();},
+  getMilliseconds: function(d) {return d.getMilliseconds();},
+  getDay:          function(d) {return d.getDay();},
+  makeDate:        function(y, m, d, hh, mm, ss, ms) {
+    return new Date(y, m, d, hh, mm, ss, ms);
+  }
+};
+
+/**
+ * Date accessors to get the parts of a calendar date (year, month, 
+ * day of month, hour, minute, second and millisecond) according to UTC time,
+ * and factory method to call the Date constructor with an array of arguments.
+ */
+Dygraph.DateAccessorsUTC = {
+  getFullYear:     function(d) {return d.getUTCFullYear();},
+  getMonth:        function(d) {return d.getUTCMonth();},
+  getDate:         function(d) {return d.getUTCDate();},
+  getHours:        function(d) {return d.getUTCHours();},
+  getMinutes:      function(d) {return d.getUTCMinutes();},
+  getSeconds:      function(d) {return d.getUTCSeconds();},
+  getMilliseconds: function(d) {return d.getUTCMilliseconds();},
+  getDay:          function(d) {return d.getUTCDay();},
+  makeDate:        function(y, m, d, hh, mm, ss, ms) {
+    return new Date(Date.UTC(y, m, d, hh, mm, ss, ms));
+  }
+};
+
+/**
  * Return a string version of the hours, minutes and seconds portion of a date.
- *
- * @param {number} date The JavaScript date (ms since epoch)
- * @return {string} A time of the form "HH:MM:SS"
+ * @param {number} hh The hours (from 0-23)
+ * @param {number} mm The minutes (from 0-59)
+ * @param {number} ss The seconds (from 0-59)
+ * @return {string} A time of the form "HH:MM" or "HH:MM:SS"
  * @private
  */
-Dygraph.hmsString_ = function(date) {
+Dygraph.hmsString_ = function(hh, mm, ss) {
   var zeropad = Dygraph.zeropad;
-  var d = new Date(date);
-  if (d.getSeconds()) {
-    return zeropad(d.getHours()) + ":" +
-           zeropad(d.getMinutes()) + ":" +
-           zeropad(d.getSeconds());
-  } else {
-    return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes());
+  var ret = zeropad(hh) + ":" + zeropad(mm);
+  if (ss) {
+    ret += ":" + zeropad(ss);
   }
+  return ret;
 };
 
 /**
- * Convert a JS date (millis since epoch) to YYYY/MM/DD
- * @param {number} date The JavaScript date (ms since epoch)
- * @return {string} A date of the form "YYYY/MM/DD"
+ * Convert a JS date (millis since epoch) to a formatted string.
+ * @param {number} time The JavaScript time value (ms since epoch)
+ * @param {boolean} utc Wether output UTC or local time
+ * @return {string} A date of one of these forms:
+ *     "YYYY/MM/DD", "YYYY/MM/DD HH:MM" or "YYYY/MM/DD HH:MM:SS"
  * @private
  */
-Dygraph.dateString_ = function(date) {
+Dygraph.dateString_ = function(time, utc) {
   var zeropad = Dygraph.zeropad;
-  var d = new Date(date);
-
-  // Get the year:
-  var year = "" + d.getFullYear();
+  var accessors = utc ? Dygraph.DateAccessorsUTC : Dygraph.DateAccessorsLocal;
+  var date = new Date(time);
+  var y = accessors.getFullYear(date);
+  var m = accessors.getMonth(date);
+  var d = accessors.getDate(date);
+  var hh = accessors.getHours(date);
+  var mm = accessors.getMinutes(date);
+  var ss = accessors.getSeconds(date);
+  // Get a year string:
+  var year = "" + y;
   // Get a 0 padded month string
-  var month = zeropad(d.getMonth() + 1);  //months are 0-offset, sigh
+  var month = zeropad(m + 1);  //months are 0-offset, sigh
   // Get a 0 padded day string
-  var day = zeropad(d.getDate());
-
-  var ret = "";
-  var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
-  if (frac) ret = " " + Dygraph.hmsString_(date);
-
-  return year + "/" + month + "/" + day + ret;
+  var day = zeropad(d);
+  var frac = hh * 3600 + mm * 60 + ss;
+  var ret = year + "/" + month + "/" + day;
+  if (frac) {
+    ret += " " + Dygraph.hmsString_(hh, mm, ss);
+  }
+  return ret;
 };
 
 /**
@@ -628,7 +571,7 @@ Dygraph.dateParser = function(dateStr) {
   }
 
   if (!d || isNaN(d)) {
-    Dygraph.error("Couldn't parse " + dateStr + " as a date");
+    console.error("Couldn't parse " + dateStr + " as a date");
   }
   return d;
 };
@@ -787,13 +730,20 @@ Dygraph.createCanvas = function() {
  */
 Dygraph.getContextPixelRatio = function(context) {
   try {
-    var devicePixelRatio = window.devicePixelRatio || 1,
-        backingStoreRatio = context.webkitBackingStorePixelRatio ||
+    var devicePixelRatio = window.devicePixelRatio;
+    var backingStoreRatio = context.webkitBackingStorePixelRatio ||
                             context.mozBackingStorePixelRatio ||
                             context.msBackingStorePixelRatio ||
                             context.oBackingStorePixelRatio ||
                             context.backingStorePixelRatio || 1;
-    return devicePixelRatio / backingStoreRatio;
+    if (devicePixelRatio !== undefined) {
+      return devicePixelRatio / backingStoreRatio;
+    } else {
+      // At least devicePixelRatio must be defined for this ratio to make sense.
+      // We default backingStoreRatio to 1: this does not exist on some browsers
+      // (i.e. desktop Chrome).
+      return 1;
+    }
   } catch (e) {
     return 1;
   }
@@ -945,72 +895,64 @@ Dygraph.repeatAndCleanup = function(repeatFn, maxFrames, framePeriodInMillis,
   })();
 };
 
+// A whitelist of options that do not change pixel positions.
+var pixelSafeOptions = {
+  'annotationClickHandler': true,
+  'annotationDblClickHandler': true,
+  'annotationMouseOutHandler': true,
+  'annotationMouseOverHandler': true,
+  'axisLabelColor': true,
+  'axisLineColor': true,
+  'axisLineWidth': true,
+  'clickCallback': true,
+  'drawCallback': true,
+  'drawHighlightPointCallback': true,
+  'drawPoints': true,
+  'drawPointCallback': true,
+  'drawXGrid': true,
+  'drawYGrid': true,
+  'fillAlpha': true,
+  'gridLineColor': true,
+  'gridLineWidth': true,
+  'hideOverlayOnMouseOut': true,
+  'highlightCallback': true,
+  'highlightCircleSize': true,
+  'interactionModel': true,
+  'isZoomedIgnoreProgrammaticZoom': true,
+  'labelsDiv': true,
+  'labelsDivStyles': true,
+  'labelsDivWidth': true,
+  'labelsKMB': true,
+  'labelsKMG2': true,
+  'labelsSeparateLines': true,
+  'labelsShowZeroValues': true,
+  'legend': true,
+  'panEdgeFraction': true,
+  'pixelsPerYLabel': true,
+  'pointClickCallback': true,
+  'pointSize': true,
+  'rangeSelectorPlotFillColor': true,
+  'rangeSelectorPlotStrokeColor': true,
+  'showLabelsOnHighlight': true,
+  'showRoller': true,
+  'strokeWidth': true,
+  'underlayCallback': true,
+  'unhighlightCallback': true,
+  'zoomCallback': true
+};
+
 /**
  * This function will scan the option list and determine if they
  * require us to recalculate the pixel positions of each point.
+ * TODO: move this into dygraph-options.js
  * @param {!Array.<string>} labels a list of options to check.
  * @param {!Object} attrs
  * @return {boolean} true if the graph needs new points else false.
  * @private
  */
 Dygraph.isPixelChangingOptionList = function(labels, attrs) {
-  // A whitelist of options that do not change pixel positions.
-  var pixelSafeOptions = {
-    'annotationClickHandler': true,
-    'annotationDblClickHandler': true,
-    'annotationMouseOutHandler': true,
-    'annotationMouseOverHandler': true,
-    'axisLabelColor': true,
-    'axisLineColor': true,
-    'axisLineWidth': true,
-    'clickCallback': true,
-    'digitsAfterDecimal': true,
-    'drawCallback': true,
-    'drawHighlightPointCallback': true,
-    'drawPoints': true,
-    'drawPointCallback': true,
-    'drawXGrid': true,
-    'drawYGrid': true,
-    'fillAlpha': true,
-    'gridLineColor': true,
-    'gridLineWidth': true,
-    'hideOverlayOnMouseOut': true,
-    'highlightCallback': true,
-    'highlightCircleSize': true,
-    'interactionModel': true,
-    'isZoomedIgnoreProgrammaticZoom': true,
-    'labelsDiv': true,
-    'labelsDivStyles': true,
-    'labelsDivWidth': true,
-    'labelsKMB': true,
-    'labelsKMG2': true,
-    'labelsSeparateLines': true,
-    'labelsShowZeroValues': true,
-    'legend': true,
-    'maxNumberWidth': true,
-    'panEdgeFraction': true,
-    'pixelsPerYLabel': true,
-    'pointClickCallback': true,
-    'pointSize': true,
-    'rangeSelectorPlotFillColor': true,
-    'rangeSelectorPlotStrokeColor': true,
-    'showLabelsOnHighlight': true,
-    'showRoller': true,
-    'sigFigs': true,
-    'strokeWidth': true,
-    'underlayCallback': true,
-    'unhighlightCallback': true,
-    'xAxisLabelFormatter': true,
-    'xTicker': true,
-    'xValueFormatter': true,
-    'yAxisLabelFormatter': true,
-    'yValueFormatter': true,
-    'zoomCallback': true
-  };
-
   // Assume that we do not require new points.
   // This will change to true if we actually do need new points.
-  var requiresNewPoints = false;
 
   // Create a dictionary of series names for faster lookup.
   // If there are no labels, then the dictionary stays empty.
@@ -1021,34 +963,44 @@ Dygraph.isPixelChangingOptionList = function(labels, attrs) {
     }
   }
 
+  // Scan through a flat (i.e. non-nested) object of options.
+  // Returns true/false depending on whether new points are needed.
+  var scanFlatOptions = function(options) {
+    for (var property in options) {
+      if (options.hasOwnProperty(property) &&
+          !pixelSafeOptions[property]) {
+        return true;
+      }
+    }
+    return false;
+  };
+
   // Iterate through the list of updated options.
   for (var property in attrs) {
-    // Break early if we already know we need new points from a previous option.
-    if (requiresNewPoints) {
-      break;
-    }
-    if (attrs.hasOwnProperty(property)) {
-      // Find out of this field is actually a series specific options list.
-      if (seriesNamesDictionary[property]) {
-        // This property value is a list of options for this series.
-        // If any of these sub properties are not pixel safe, set the flag.
-        for (var subProperty in attrs[property]) {
-          // Break early if we already know we need new points from a previous option.
-          if (requiresNewPoints) {
-            break;
-          }
-          if (attrs[property].hasOwnProperty(subProperty) && !pixelSafeOptions[subProperty]) {
-            requiresNewPoints = true;
-          }
+    if (!attrs.hasOwnProperty(property)) continue;
+
+    // Find out of this field is actually a series specific options list.
+    if (property == 'highlightSeriesOpts' ||
+        (seriesNamesDictionary[property] && !attrs.series)) {
+      // This property value is a list of options for this series.
+      if (scanFlatOptions(attrs[property])) return true;
+    } else if (property == 'series' || property == 'axes') {
+      // This is twice-nested options list.
+      var perSeries = attrs[property];
+      for (var series in perSeries) {
+        if (perSeries.hasOwnProperty(series) &&
+            scanFlatOptions(perSeries[series])) {
+          return true;
         }
-      // If this was not a series specific option list, check if its a pixel changing property.
-      } else if (!pixelSafeOptions[property]) {
-        requiresNewPoints = true;
       }
+    } else {
+      // If this was not a series specific option list, check if it's a pixel
+      // changing property.
+      if (!pixelSafeOptions[property]) return true;
     }
   }
 
-  return requiresNewPoints;
+  return false;
 };
 
 Dygraph.Circles = {
@@ -1183,37 +1135,6 @@ Dygraph.pow = function(base, exp) {
   return Math.pow(base, exp);
 };
 
-// For Dygraph.setDateSameTZ, below.
-Dygraph.dateSetters = {
-  ms: Date.prototype.setMilliseconds,
-  s: Date.prototype.setSeconds,
-  m: Date.prototype.setMinutes,
-  h: Date.prototype.setHours
-};
-
-/**
- * This is like calling d.setSeconds(), d.setMinutes(), etc, except that it
- * adjusts for time zone changes to keep the date/time parts consistent.
- *
- * For example, d.getSeconds(), d.getMinutes() and d.getHours() will all be
- * the same before/after you call setDateSameTZ(d, {ms: 0}). The same is not
- * true if you call d.setMilliseconds(0).
- *
- * @type {function(!Date, Object.<number>)}
- */
-Dygraph.setDateSameTZ = function(d, parts) {
-  var tz = d.getTimezoneOffset();
-  for (var k in parts) {
-    if (!parts.hasOwnProperty(k)) continue;
-    var setter = Dygraph.dateSetters[k];
-    if (!setter) throw "Invalid setter: " + k;
-    setter.call(d, parts[k]);
-    if (d.getTimezoneOffset() != tz) {
-      d.setTime(d.getTime() + (tz - d.getTimezoneOffset()) * 60 * 1000);
-    }
-  }
-};
-
 /**
  * Converts any valid CSS color (hex, rgb(), named color) to an RGB tuple.
  *
@@ -1227,7 +1148,13 @@ Dygraph.toRGB_ = function(colorStr) {
   div.style.backgroundColor = colorStr;
   div.style.visibility = 'hidden';
   document.body.appendChild(div);
-  var rgbStr = window.getComputedStyle(div, null).backgroundColor;
+  var rgbStr;
+  if (window.getComputedStyle) {
+    rgbStr = window.getComputedStyle(div, null).backgroundColor;
+  } else {
+    // IE8
+    rgbStr = div.currentStyle.backgroundColor;
+  }
   document.body.removeChild(div);
   var bits = /^rgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)$/.exec(rgbStr);
   return {
@@ -1285,7 +1212,9 @@ Dygraph.parseFloat_ = function(x, opt_line_no, opt_line) {
   if (opt_line !== undefined && opt_line_no !== undefined) {
     msg += " on line " + (1+(opt_line_no||0)) + " ('" + opt_line + "') of CSV.";
   }
-  Dygraph.error(msg);
+  console.error(msg);
 
   return null;
 };
+
+})();
index d95f9be..65634eb 100644 (file)
 
  */
 
-/*jshint globalstrict: true */
+// For "production" code, this gets set to false by uglifyjs.
+if (typeof(DEBUG) === 'undefined') DEBUG=true;
+
+var Dygraph = (function() {
 /*global DygraphLayout:false, DygraphCanvasRenderer:false, DygraphOptions:false, G_vmlCanvasManager:false,ActiveXObject:false */
 "use strict";
 
@@ -72,7 +75,7 @@ var Dygraph = function(div, data, opts, opt_fourth_param) {
     // Old versions of dygraphs took in the series labels as a constructor
     // parameter. This doesn't make sense anymore, but it's easy to continue
     // to support this usage.
-    Dygraph.warn("Using deprecated four-argument dygraph constructor");
+    console.warn("Using deprecated four-argument dygraph constructor");
     this.__old_init__(div, data, opts, opt_fourth_param);
   } else {
     this.__init__(div, data, opts);
@@ -114,10 +117,8 @@ Dygraph.KMG2_SMALL_LABELS = [ 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y' ];
  * and maxNumberWidth options.
  * @param {number} x The number to be formatted
  * @param {Dygraph} opts An options view
- * @param {string} name The name of the point's data series
- * @param {Dygraph} g The dygraph object
  */
-Dygraph.numberValueFormatter = function(x, opts, pt, g) {
+Dygraph.numberValueFormatter = function(x, opts) {
   var sigFigs = opts('sigFigs');
 
   if (sigFigs !== null) {
@@ -151,7 +152,7 @@ Dygraph.numberValueFormatter = function(x, opts, pt, g) {
       k_labels = Dygraph.KMB_LABELS;
     }
     if (kmg2) {
-      if (kmb) Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
+      if (kmb) console.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
       k = 1024;
       k_labels = Dygraph.KMG2_BIG_LABELS;
       m_labels = Dygraph.KMG2_SMALL_LABELS;
@@ -188,8 +189,8 @@ Dygraph.numberValueFormatter = function(x, opts, pt, g) {
  * variant for use as an axisLabelFormatter.
  * @private
  */
-Dygraph.numberAxisLabelFormatter = function(x, granularity, opts, g) {
-  return Dygraph.numberValueFormatter(x, opts, g);
+Dygraph.numberAxisLabelFormatter = function(x, granularity, opts) {
+  return Dygraph.numberValueFormatter(x, opts);
 };
 
 /**
@@ -202,28 +203,53 @@ Dygraph.SHORT_MONTH_NAMES_ = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', '
 
 /**
  * Convert a JS date to a string appropriate to display on an axis that
- * is displaying values at the stated granularity.
+ * is displaying values at the stated granularity. This respects the 
+ * labelsUTC option.
  * @param {Date} date The date to format
  * @param {number} granularity One of the Dygraph granularity constants
- * @return {string} The formatted date
+ * @param {Dygraph} opts An options view
+ * @return {string} The date formatted as local time
  * @private
  */
-Dygraph.dateAxisFormatter = function(date, granularity) {
+Dygraph.dateAxisLabelFormatter = function(date, granularity, opts) {
+  var utc = opts('labelsUTC');
+  var accessors = utc ? Dygraph.DateAccessorsUTC : Dygraph.DateAccessorsLocal;
+
+  var year = accessors.getFullYear(date),
+      month = accessors.getMonth(date),
+      day = accessors.getDate(date),
+      hours = accessors.getHours(date),
+      mins = accessors.getMinutes(date),
+      secs = accessors.getSeconds(date),
+      millis = accessors.getSeconds(date);
+
   if (granularity >= Dygraph.DECADAL) {
-    return '' + date.getFullYear();
+    return '' + year;
   } else if (granularity >= Dygraph.MONTHLY) {
-    return Dygraph.SHORT_MONTH_NAMES_[date.getMonth()] + ' ' + date.getFullYear();
+    return Dygraph.SHORT_MONTH_NAMES_[month] + ' ' + year;
   } else {
-    var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds();
+    var frac = hours * 3600 + mins * 60 + secs + 1e-3 * millis;
     if (frac === 0 || granularity >= Dygraph.DAILY) {
       // e.g. '21Jan' (%d%b)
-      var nd = new Date(date.getTime() + 3600*1000);
-      return Dygraph.zeropad(nd.getDate()) + Dygraph.SHORT_MONTH_NAMES_[nd.getMonth()];
+      return Dygraph.zeropad(day) + Dygraph.SHORT_MONTH_NAMES_[month];
     } else {
-      return Dygraph.hmsString_(date.getTime());
+      return Dygraph.hmsString_(hours, mins, secs);
     }
   }
 };
+// alias in case anyone is referencing the old method.
+Dygraph.dateAxisFormatter = Dygraph.dateAxisLabelFormatter;
+
+/**
+ * Return a string version of a JS date for a value label. This respects the 
+ * labelsUTC option.
+ * @param {Date} date The date to be formatted
+ * @param {Dygraph} opts An options view
+ * @private
+ */
+Dygraph.dateValueFormatter = function(d, opts) {
+  return Dygraph.dateString_(d, opts('labelsUTC'));
+};
 
 /**
  * Standard plotters. These may be used by clients.
@@ -307,7 +333,6 @@ Dygraph.DEFAULT_ATTRS = {
   axisLineWidth: 0.3,
   gridLineWidth: 0.3,
   axisLabelColor: "black",
-  axisLabelFont: "Arial",  // TODO(danvk): is this implemented?
   axisLabelWidth: 50,
   drawYGrid: true,
   drawXGrid: true,
@@ -321,6 +346,7 @@ Dygraph.DEFAULT_ATTRS = {
   rangeSelectorHeight: 40,
   rangeSelectorPlotStrokeColor: "#808FAB",
   rangeSelectorPlotFillColor: "#A7B1C4",
+  showInRangeSelector: null,
 
   // The ordering here ensures that central lines always appear above any
   // fill bars/error bars.
@@ -336,8 +362,8 @@ Dygraph.DEFAULT_ATTRS = {
   axes: {
     x: {
       pixelsPerLabel: 60,
-      axisLabelFormatter: Dygraph.dateAxisFormatter,
-      valueFormatter: Dygraph.dateString_,
+      axisLabelFormatter: Dygraph.dateAxisLabelFormatter,
+      valueFormatter: Dygraph.dateValueFormatter,
       drawGrid: true,
       drawAxis: true,
       independentTicks: true,
@@ -420,7 +446,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   }
 
   if (!div) {
-    Dygraph.error("Constructing dygraph with a non-existent div!");
+    console.error("Constructing dygraph with a non-existent div!");
     return;
   }
 
@@ -508,8 +534,16 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.plugins_ = [];
   var plugins = Dygraph.PLUGINS.concat(this.getOption('plugins'));
   for (var i = 0; i < plugins.length; i++) {
-    var Plugin = plugins[i];
-    var pluginInstance = new Plugin();
+    // the plugins option may contain either plugin classes or instances.
+    // Plugin instances contain an activate method.
+    var Plugin = plugins[i];  // either a constructor or an instance.
+    var pluginInstance;
+    if (typeof(Plugin.activate) !== 'undefined') {
+      pluginInstance = Plugin;
+    } else {
+      pluginInstance = new Plugin();
+    }
+
     var pluginDict = {
       plugin: pluginInstance,
       events: {},
@@ -519,6 +553,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
 
     var handlers = pluginInstance.activate(this);
     for (var eventName in handlers) {
+      if (!handlers.hasOwnProperty(eventName)) continue;
       // TODO(danvk): validate eventName.
       pluginDict.events[eventName] = handlers[eventName];
     }
@@ -550,12 +585,12 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
 
 /**
  * Triggers a cascade of events to the various plugins which are interested in them.
- * Returns true if the "default behavior" should be performed, i.e. if none of
- * the event listeners called event.preventDefault().
+ * Returns true if the "default behavior" should be prevented, i.e. if one
+ * of the event listeners called event.preventDefault().
  * @private
  */
 Dygraph.prototype.cascadeEvents_ = function(name, extra_props) {
-  if (!(name in this.eventListeners_)) return true;
+  if (!(name in this.eventListeners_)) return false;
 
   // QUESTION: can we use objects & prototypes to speed this up?
   var e = {
@@ -640,16 +675,16 @@ Dygraph.prototype.toString = function() {
  * @return { ... } The value of the option.
  */
 Dygraph.prototype.attr_ = function(name, seriesName) {
-// <REMOVE_FOR_COMBINED>
-  if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
-    Dygraph.error('Must include options reference JS for testing');
-  } else if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(name)) {
-    Dygraph.error('Dygraphs is using property ' + name + ', which has no ' +
-                  'entry in the Dygraphs.OPTIONS_REFERENCE listing.');
-    // Only log this error once.
-    Dygraph.OPTIONS_REFERENCE[name] = true;
-  }
-// </REMOVE_FOR_COMBINED>
+  if (DEBUG) {
+    if (typeof(Dygraph.OPTIONS_REFERENCE) === 'undefined') {
+      console.error('Must include options reference JS for testing');
+    } else if (!Dygraph.OPTIONS_REFERENCE.hasOwnProperty(name)) {
+      console.error('Dygraphs is using property ' + name + ', which has no ' +
+                    'entry in the Dygraphs.OPTIONS_REFERENCE listing.');
+      // Only log this error once.
+      Dygraph.OPTIONS_REFERENCE[name] = true;
+    }
+  }
   return seriesName ? this.attributes_.getForSeries(name, seriesName) : this.attributes_.get(name);
 };
 
@@ -735,6 +770,14 @@ Dygraph.prototype.optionsViewForAxis_ = function(axis) {
     if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {
       return axis_opts[axis][opt];
     }
+
+    // I don't like that this is in a second spot.
+    if (axis === 'x' && opt === 'logscale') {
+      // return the default value.
+      // TODO(konigsberg): pull the default from a global default.
+      return false;
+    }
+
     // user-specified attributes always trump defaults, even if they're less
     // specific.
     if (typeof(self.user_attrs_[opt]) != 'undefined') {
@@ -893,7 +936,37 @@ Dygraph.prototype.toDataXCoord = function(x) {
 
   var area = this.plotter_.area;
   var xRange = this.xAxisRange();
-  return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
+
+  if (!this.attributes_.getForAxis("logscale", 'x')) {
+    return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
+  } else {
+    // TODO: remove duplicate code?
+    // Computing the inverse of toDomCoord.
+    var pct = (x - area.x) / area.w;
+
+    // Computing the inverse of toPercentXCoord. The function was arrived at with
+    // the following steps:
+    //
+    // Original calcuation:
+    // pct = (log(x) - log(xRange[0])) / (log(xRange[1]) - log(xRange[0])));
+    //
+    // Multiply both sides by the right-side demoninator.
+    // pct * (log(xRange[1] - log(xRange[0]))) = log(x) - log(xRange[0])
+    //
+    // add log(xRange[0]) to both sides
+    // log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])) = log(x);
+    //
+    // Swap both sides of the equation,
+    // log(x) = log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0]))
+    //
+    // Use both sides as the exponent in 10^exp and we're done.
+    // x = 10 ^ (log(xRange[0]) + (pct * (log(xRange[1]) - log(xRange[0])))
+    var logr0 = Dygraph.log10(xRange[0]);
+    var logr1 = Dygraph.log10(xRange[1]);
+    var exponent = logr0 + (pct * (logr1 - logr0));
+    var value = Math.pow(Dygraph.LOG_SCALE, exponent);
+    return value;
+  }
 };
 
 /**
@@ -921,21 +994,25 @@ Dygraph.prototype.toDataYCoord = function(y, axis) {
     // the following steps:
     //
     // Original calcuation:
-    // pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
+    // pct = (log(yRange[1]) - log(y)) / (log(yRange[1]) - log(yRange[0]));
     //
-    // Move denominator to both sides:
-    // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y);
+    // Multiply both sides by the right-side demoninator.
+    // pct * (log(yRange[1]) - log(yRange[0])) = log(yRange[1]) - log(y);
     //
-    // subtract logr1, and take the negative value.
-    // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y);
+    // subtract log(yRange[1]) from both sides.
+    // (pct * (log(yRange[1]) - log(yRange[0]))) - log(yRange[1]) = -log(y);
     //
-    // Swap both sides of the equation, and we can compute the log of the
-    // return value. Which means we just need to use that as the exponent in
-    // e^exponent.
-    // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
-
+    // and multiply both sides by -1.
+    // log(yRange[1]) - (pct * (logr1 - log(yRange[0])) = log(y);
+    //
+    // Swap both sides of the equation,
+    // log(y) = log(yRange[1]) - (pct * (log(yRange[1]) - log(yRange[0])));
+    //
+    // Use both sides as the exponent in 10^exp and we're done.
+    // y = 10 ^ (log(yRange[1]) - (pct * (log(yRange[1]) - log(yRange[0]))));
+    var logr0 = Dygraph.log10(yRange[0]);
     var logr1 = Dygraph.log10(yRange[1]);
-    var exponent = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
+    var exponent = logr1 - (pct * (logr1 - logr0));
     var value = Math.pow(Dygraph.LOG_SCALE, exponent);
     return value;
   }
@@ -967,14 +1044,15 @@ Dygraph.prototype.toPercentYCoord = function(y, axis) {
 
   var pct;
   var logscale = this.attributes_.getForAxis("logscale", axis);
-  if (!logscale) {
+  if (logscale) {
+    var logr0 = Dygraph.log10(yRange[0]);
+    var logr1 = Dygraph.log10(yRange[1]);
+    pct = (logr1 - Dygraph.log10(y)) / (logr1 - logr0);
+  } else {
     // yRange[1] - y is unit distance from the bottom.
     // yRange[1] - yRange[0] is the scale of the range.
     // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.
     pct = (yRange[1] - y) / (yRange[1] - yRange[0]);
-  } else {
-    var logr1 = Dygraph.log10(yRange[1]);
-    pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
   }
   return pct;
 };
@@ -998,7 +1076,19 @@ Dygraph.prototype.toPercentXCoord = function(x) {
   }
 
   var xRange = this.xAxisRange();
-  return (x - xRange[0]) / (xRange[1] - xRange[0]);
+  var pct;
+  var logscale = this.attributes_.getForAxis("logscale", 'x') ;
+  if (logscale === true) {  // logscale can be null so we test for true explicitly.
+    var logr0 = Dygraph.log10(xRange[0]);
+    var logr1 = Dygraph.log10(xRange[1]);
+    pct = (Dygraph.log10(x) - logr0) / (logr1 - logr0);
+  } else {
+    // x - xRange[0] is unit distance from the left.
+    // xRange[1] - xRange[0] is the scale of the range.
+    // The full expression below is the % from the left.
+    pct = (x - xRange[0]) / (xRange[1] - xRange[0]);
+  }
+  return pct;
 };
 
 /**
@@ -1050,6 +1140,7 @@ Dygraph.prototype.createInterface_ = function() {
 
   // TODO(danvk): any other styles that are useful to set here?
   this.graphDiv.style.textAlign = 'left';  // This is a CSS "reset"
+  this.graphDiv.style.position = 'relative';
   enclosing.appendChild(this.graphDiv);
 
   // Create the canvas for interactive parts of the chart.
@@ -1138,6 +1229,12 @@ Dygraph.prototype.destroy = function() {
   this.canvas_ctx_.restore();
   this.hidden_ctx_.restore();
 
+  // Destroy any plugins, in the reverse order that they were registered.
+  for (var i = this.plugins_.length - 1; i >= 0; i--) {
+    var p = this.plugins_.pop();
+    if (p.plugin.destroy) p.plugin.destroy();
+  }
+
   var removeRecursive = function(node) {
     while (node.hasChildNodes()) {
       removeRecursive(node.firstChild);
@@ -1152,7 +1249,7 @@ Dygraph.prototype.destroy = function() {
   Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
 
   // remove window handlers
-  Dygraph.removeEvent(window,'resize',this.resizeHandler_);
+  Dygraph.removeEvent(window,'resize', this.resizeHandler_);
   this.resizeHandler_ = null;
 
   removeRecursive(this.maindiv_);
@@ -1391,6 +1488,26 @@ Dygraph.prototype.createDragInterface_ = function() {
       contextB.dragStartY = Dygraph.dragGetY_(event, contextB);
       contextB.cancelNextDblclick = false;
       contextB.tarp.cover();
+    },
+    destroy: function() {
+      var context = this;
+      if (context.isZooming || context.isPanning) {
+        context.isZooming = false;
+        context.dragStartX = null;
+        context.dragStartY = null;
+      }
+
+      if (context.isPanning) {
+        context.isPanning = false;
+        context.draggingDate = null;
+        context.dateRange = null;
+        for (var i = 0; i < self.axes_.length; i++) {
+          delete self.axes_[i].draggingValue;
+          delete self.axes_[i].dragValueRange;
+        }
+      }
+
+      context.tarp.uncover();
     }
   };
 
@@ -1414,27 +1531,13 @@ Dygraph.prototype.createDragInterface_ = function() {
 
   // If the user releases the mouse button during a drag, but not over the
   // canvas, then it doesn't count as a zooming action.
-  var mouseUpHandler = function(event) {
-    if (context.isZooming || context.isPanning) {
-      context.isZooming = false;
-      context.dragStartX = null;
-      context.dragStartY = null;
-    }
-
-    if (context.isPanning) {
-      context.isPanning = false;
-      context.draggingDate = null;
-      context.dateRange = null;
-      for (var i = 0; i < self.axes_.length; i++) {
-        delete self.axes_[i].draggingValue;
-        delete self.axes_[i].dragValueRange;
-      }
-    }
-
-    context.tarp.uncover();
-  };
+  if (!interactionModel.willDestroyContextMyself) {
+    var mouseUpHandler = function(event) {
+      context.destroy();
+    };
 
-  this.addAndTrackEvent(document, 'mouseup', mouseUpHandler);
+    this.addAndTrackEvent(document, 'mouseup', mouseUpHandler);
+  }
 };
 
 /**
@@ -1499,7 +1602,7 @@ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY,
  */
 Dygraph.prototype.clearZoomRect_ = function() {
   this.currentZoomRectArgs_ = null;
-  this.canvas_ctx_.clearRect(0, 0, this.canvas_.width, this.canvas_.height);
+  this.canvas_ctx_.clearRect(0, 0, this.width_, this.height_);
 };
 
 /**
@@ -1522,16 +1625,6 @@ Dygraph.prototype.doZoomX_ = function(lowX, highX) {
 };
 
 /**
- * Transition function to use in animations. Returns values between 0.0
- * (totally old values) and 1.0 (totally new values) for each frame.
- * @private
- */
-Dygraph.zoomAnimationFunction = function(frame, numFrames) {
-  var k = 1.5;
-  return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames));
-};
-
-/**
  * Zoom to something containing [minDate, maxDate] values. Don't confuse this
  * method with doZoomX which accepts pixel coordinates. This function redraws
  * the graph.
@@ -1541,8 +1634,8 @@ Dygraph.zoomAnimationFunction = function(frame, numFrames) {
  * @private
  */
 Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) {
-  // TODO(danvk): when yAxisRange is null (i.e. "fit to data", the animation
-  // can produce strange effects. Rather than the y-axis transitioning slowly
+  // TODO(danvk): when xAxisRange is null (i.e. "fit to data", the animation
+  // can produce strange effects. Rather than the x-axis transitioning slowly
   // between values, it can jerk around.)
   var old_window = this.xAxisRange();
   var new_window = [minDate, maxDate];
@@ -1550,7 +1643,7 @@ Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) {
   var that = this;
   this.doAnimatedZoom(old_window, new_window, null, null, function() {
     if (that.getFunctionOption("zoomCallback")) {
-      that.getFunctionOption("zoomCallback")(
+      that.getFunctionOption("zoomCallback").call(that,
           minDate, maxDate, that.yAxisRanges());
     }
   });
@@ -1583,13 +1676,23 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) {
   this.doAnimatedZoom(null, null, oldValueRanges, newValueRanges, function() {
     if (that.getFunctionOption("zoomCallback")) {
       var xRange = that.xAxisRange();
-      that.getFunctionOption("zoomCallback")(
+      that.getFunctionOption("zoomCallback").call(that,
           xRange[0], xRange[1], that.yAxisRanges());
     }
   });
 };
 
 /**
+ * Transition function to use in animations. Returns values between 0.0
+ * (totally old values) and 1.0 (totally new values) for each frame.
+ * @private
+ */
+Dygraph.zoomAnimationFunction = function(frame, numFrames) {
+  var k = 1.5;
+  return (1.0 - Math.pow(k, -frame)) / (1.0 - Math.pow(k, -numFrames));
+};
+
+/**
  * Reset the zoom to the original view coordinates. This is the same as
  * double-clicking on the graph.
  */
@@ -1628,7 +1731,7 @@ Dygraph.prototype.resetZoom = function() {
       }
       this.drawGraph_();
       if (this.getFunctionOption("zoomCallback")) {
-        this.getFunctionOption("zoomCallback")(
+        this.getFunctionOption("zoomCallback").call(this,
             minDate, maxDate, this.yAxisRanges());
       }
       return;
@@ -1671,7 +1774,7 @@ Dygraph.prototype.resetZoom = function() {
             }
           }
           if (that.getFunctionOption("zoomCallback")) {
-            that.getFunctionOption("zoomCallback")(
+            that.getFunctionOption("zoomCallback").call(that,
                 minDate, maxDate, that.yAxisRanges());
           }
         });
@@ -1908,7 +2011,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
 
   var callback = this.getFunctionOption("highlightCallback");
   if (callback && selectionChanged) {
-    callback(event,
+    callback.call(this, event,
         this.lastx_,
         this.selPoints_,
         this.lastRow_,
@@ -2038,7 +2141,7 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) {
       ctx.lineWidth = this.getNumericOption('strokeWidth', pt.name);
       ctx.strokeStyle = color;
       ctx.fillStyle = color;
-      callback(this, pt.name, ctx, canvasx, pt.canvasy,
+      callback.call(this, this, pt.name, ctx, canvasx, pt.canvasy,
           color, circleSize, pt.idx);
     }
     ctx.restore();
@@ -2069,10 +2172,23 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName, opt_locked) {
     this.lastRow_ = row;
     for (var setIdx = 0; setIdx < this.layout_.points.length; ++setIdx) {
       var points = this.layout_.points[setIdx];
+      // Check if the point at the appropriate index is the point we're looking
+      // for.  If it is, just use it, otherwise search the array for a point
+      // in the proper place.
       var setRow = row - this.getLeftBoundary_(setIdx);
-      if (setRow < points.length) {
+      if (setRow < points.length && points[setRow].idx == row) {
         var point = points[setRow];
         if (point.yval !== null) this.selPoints_.push(point);
+      } else {
+        for (var pointIdx = 0; pointIdx < points.length; ++pointIdx) {
+          var point = points[pointIdx];
+          if (point.idx == row) {
+            if (point.yval !== null) {
+              this.selPoints_.push(point);
+            }
+            break;
+          }
+        }
       }
     }
   } else {
@@ -2108,10 +2224,10 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName, opt_locked) {
  */
 Dygraph.prototype.mouseOut_ = function(event) {
   if (this.getFunctionOption("unhighlightCallback")) {
-    this.getFunctionOption("unhighlightCallback")(event);
+    this.getFunctionOption("unhighlightCallback").call(this, event);
   }
 
-  if (this.getFunctionOption("hideOverlayOnMouseOut") && !this.lockedSet_) {
+  if (this.getBooleanOption("hideOverlayOnMouseOut") && !this.lockedSet_) {
     this.clearSelection();
   }
 };
@@ -2181,6 +2297,7 @@ Dygraph.prototype.isSeriesLocked = function() {
  */
 Dygraph.prototype.loadedEvent_ = function(data) {
   this.rawData_ = this.parseCSV_(data);
+  this.cascadeDataDidUpdateEvent_();
   this.predraw_();
 };
 
@@ -2252,12 +2369,6 @@ Dygraph.prototype.predraw_ = function() {
   // TODO(danvk): move more computations out of drawGraph_ and into here.
   this.computeYAxes_();
 
-  // Create a new plotter.
-  if (this.plotter_) {
-    this.cascadeEvents_('clearChart');
-    this.plotter_.clear();
-  }
-
   if (!this.is_initial_draw_) {
     this.canvas_ctx_.restore();
     this.hidden_ctx_.restore();
@@ -2266,6 +2377,7 @@ Dygraph.prototype.predraw_ = function() {
   this.canvas_ctx_.save();
   this.hidden_ctx_.save();
 
+  // Create a new plotter.
   this.plotter_ = new DygraphCanvasRenderer(this,
                                             this.hidden_,
                                             this.hidden_ctx_,
@@ -2444,6 +2556,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
   var extremes = {};  // series name -> [low, high]
   var seriesIdx, sampleIdx;
   var firstIdx, lastIdx;
+  var axisIdx;
   
   // Loop over the fields (series).  Go from the last to the first,
   // because if they're stacked that's how we accumulate the values.
@@ -2514,7 +2627,11 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
         seriesName, boundaryIds[seriesIdx-1][0]);
 
     if (this.getBooleanOption("stackedGraph")) {
-      Dygraph.stackPoints_(seriesPoints, cumulativeYval, seriesExtremes,
+      axisIdx = this.attributes_.axisForSeries(seriesName);
+      if (cumulativeYval[axisIdx] === undefined) {
+        cumulativeYval[axisIdx] = [];
+      }
+      Dygraph.stackPoints_(seriesPoints, cumulativeYval[axisIdx], seriesExtremes,
                            this.getBooleanOption("stackedGraphNaNFill"));
     }
 
@@ -2575,7 +2692,7 @@ Dygraph.prototype.drawGraph_ = function() {
 
   if (this.getStringOption("timingName")) {
     var end = new Date();
-    Dygraph.info(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms");
+    console.log(this.getStringOption("timingName") + " - drawGraph: " + (end - start) + "ms");
   }
 };
 
@@ -2592,7 +2709,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) {
   if (this.getFunctionOption('underlayCallback')) {
     // NOTE: we pass the dygraph object to this callback twice to avoid breaking
     // users who expect a deprecated form of this callback.
-    this.getFunctionOption('underlayCallback')(
+    this.getFunctionOption('underlayCallback').call(this,
         this.hidden_ctx_, this.layout_.getPlotArea(), this, this);
   }
 
@@ -2607,8 +2724,7 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) {
 
   // TODO(danvk): is this a performance bottleneck when panning?
   // The interaction canvas should already be empty in that situation.
-  this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
-                                          this.canvas_.height);
+  this.canvas_.getContext('2d').clearRect(0, 0, this.width_, this.height_);
 
   if (this.getFunctionOption("drawCallback") !== null) {
     this.getFunctionOption("drawCallback")(this, is_initial_draw);
@@ -2919,16 +3035,16 @@ Dygraph.prototype.detectTypeFromString_ = function(str) {
 Dygraph.prototype.setXAxisOptions_ = function(isDate) {
   if (isDate) {
     this.attrs_.xValueParser = Dygraph.dateParser;
-    this.attrs_.axes.x.valueFormatter = Dygraph.dateString_;
+    this.attrs_.axes.x.valueFormatter = Dygraph.dateValueFormatter;
     this.attrs_.axes.x.ticker = Dygraph.dateTicker;
-    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter;
+    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisLabelFormatter;
   } else {
     /** @private (shut up, jsdoc!) */
     this.attrs_.xValueParser = function(x) { return parseFloat(x); };
     // TODO(danvk): use Dygraph.numberValueFormatter here?
     /** @private (shut up, jsdoc!) */
     this.attrs_.axes.x.valueFormatter = function(x) { return x; };
-    this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks;
+    this.attrs_.axes.x.ticker = Dygraph.numericTicks;
     this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;
   }
 };
@@ -2997,7 +3113,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
         // TODO(danvk): figure out an appropriate way to flag parse errors.
         vals = inFields[j].split("/");
         if (vals.length != 2) {
-          Dygraph.error('Expected fractional "num/den" values in CSV data ' +
+          console.error('Expected fractional "num/den" values in CSV data ' +
                         "but found a value '" + inFields[j] + "' on line " +
                         (1 + i) + " ('" + line + "') which is not of this form.");
           fields[j] = [0, 0];
@@ -3009,7 +3125,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
     } else if (this.getBooleanOption("errorBars")) {
       // If there are error bars, values are (value, stddev) pairs
       if (inFields.length % 2 != 1) {
-        Dygraph.error('Expected alternating (value, stdev.) pairs in CSV data ' +
+        console.error('Expected alternating (value, stdev.) pairs in CSV data ' +
                       'but line ' + (1 + i) + ' has an odd number of values (' +
                       (inFields.length - 1) + "): '" + line + "'");
       }
@@ -3030,7 +3146,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
                           Dygraph.parseFloat_(vals[1], i, line),
                           Dygraph.parseFloat_(vals[2], i, line) ];
           } else {
-            Dygraph.warn('When using customBars, values must be either blank ' +
+            console.warn('When using customBars, values must be either blank ' +
                          'or "low;center;high" tuples (got "' + val +
                          '" on line ' + (1+i));
           }
@@ -3047,7 +3163,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
     }
 
     if (fields.length != expectedCols) {
-      Dygraph.error("Number of columns in line " + i + " (" + fields.length +
+      console.error("Number of columns in line " + i + " (" + fields.length +
                     ") does not agree with number of labels (" + expectedCols +
                     ") " + line);
     }
@@ -3062,7 +3178,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
         if (fields[j]) all_null = false;
       }
       if (all_null) {
-        Dygraph.warn("The dygraphs 'labels' option is set, but the first row " +
+        console.warn("The dygraphs 'labels' option is set, but the first row " +
                      "of CSV data ('" + line + "') appears to also contain " +
                      "labels. Will drop the CSV labels and use the option " +
                      "labels.");
@@ -3073,7 +3189,7 @@ Dygraph.prototype.parseCSV_ = function(data) {
   }
 
   if (outOfOrder) {
-    Dygraph.warn("CSV is out of order; order it correctly to speed loading.");
+    console.warn("CSV is out of order; order it correctly to speed loading.");
     ret.sort(function(a,b) { return a[0] - b[0]; });
   }
 
@@ -3091,17 +3207,17 @@ Dygraph.prototype.parseCSV_ = function(data) {
 Dygraph.prototype.parseArray_ = function(data) {
   // Peek at the first x value to see if it's numeric.
   if (data.length === 0) {
-    Dygraph.error("Can't plot empty data set");
+    console.error("Can't plot empty data set");
     return null;
   }
   if (data[0].length === 0) {
-    Dygraph.error("Data set cannot contain an empty row");
+    console.error("Data set cannot contain an empty row");
     return null;
   }
 
   var i;
   if (this.attr_("labels") === null) {
-    Dygraph.warn("Using default labels. Set labels explicitly via 'labels' " +
+    console.warn("Using default labels. Set labels explicitly via 'labels' " +
                  "in the options parameter");
     this.attrs_.labels = [ "X" ];
     for (i = 1; i < data[0].length; i++) {
@@ -3111,7 +3227,7 @@ Dygraph.prototype.parseArray_ = function(data) {
   } else {
     var num_labels = this.attr_("labels");
     if (num_labels.length != data[0].length) {
-      Dygraph.error("Mismatch between number of labels (" + num_labels + ")" +
+      console.error("Mismatch between number of labels (" + num_labels + ")" +
                     " and number of columns in array (" + data[0].length + ")");
       return null;
     }
@@ -3119,21 +3235,21 @@ Dygraph.prototype.parseArray_ = function(data) {
 
   if (Dygraph.isDateLike(data[0][0])) {
     // Some intelligent defaults for a date x-axis.
-    this.attrs_.axes.x.valueFormatter = Dygraph.dateString_;
+    this.attrs_.axes.x.valueFormatter = Dygraph.dateValueFormatter;
     this.attrs_.axes.x.ticker = Dygraph.dateTicker;
-    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter;
+    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisLabelFormatter;
 
     // Assume they're all dates.
     var parsedData = Dygraph.clone(data);
     for (i = 0; i < data.length; i++) {
       if (parsedData[i].length === 0) {
-        Dygraph.error("Row " + (1 + i) + " of data is empty");
+        console.error("Row " + (1 + i) + " of data is empty");
         return null;
       }
       if (parsedData[i][0] === null ||
           typeof(parsedData[i][0].getTime) != 'function' ||
           isNaN(parsedData[i][0].getTime())) {
-        Dygraph.error("x value in row " + (1 + i) + " is not a Date");
+        console.error("x value in row " + (1 + i) + " is not a Date");
         return null;
       }
       parsedData[i][0] = parsedData[i][0].getTime();
@@ -3143,7 +3259,7 @@ Dygraph.prototype.parseArray_ = function(data) {
     // Some intelligent defaults for a numeric x-axis.
     /** @private (shut up, jsdoc!) */
     this.attrs_.axes.x.valueFormatter = function(x) { return x; };
-    this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks;
+    this.attrs_.axes.x.ticker = Dygraph.numericTicks;
     this.attrs_.axes.x.axisLabelFormatter = Dygraph.numberAxisLabelFormatter;
     return data;
   }
@@ -3178,16 +3294,16 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   var indepType = data.getColumnType(0);
   if (indepType == 'date' || indepType == 'datetime') {
     this.attrs_.xValueParser = Dygraph.dateParser;
-    this.attrs_.axes.x.valueFormatter = Dygraph.dateString_;
+    this.attrs_.axes.x.valueFormatter = Dygraph.dateValueFormatter;
     this.attrs_.axes.x.ticker = Dygraph.dateTicker;
-    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisFormatter;
+    this.attrs_.axes.x.axisLabelFormatter = Dygraph.dateAxisLabelFormatter;
   } else if (indepType == 'number') {
     this.attrs_.xValueParser = function(x) { return parseFloat(x); };
     this.attrs_.axes.x.valueFormatter = function(x) { return x; };
-    this.attrs_.axes.x.ticker = Dygraph.numericLinearTicks;
+    this.attrs_.axes.x.ticker = Dygraph.numericTicks;
     this.attrs_.axes.x.axisLabelFormatter = this.attrs_.axes.x.valueFormatter;
   } else {
-    Dygraph.error("only 'date', 'datetime' and 'number' types are supported " +
+    console.error("only 'date', 'datetime' and 'number' types are supported " +
                   "for column 1 of DataTable input (Got '" + indepType + "')");
     return null;
   }
@@ -3211,7 +3327,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
       }
       hasAnnotations = true;
     } else {
-      Dygraph.error("Only 'number' is supported as a dependent type with Gviz." +
+      console.error("Only 'number' is supported as a dependent type with Gviz." +
                     " 'string' is only supported if displayAnnotations is true");
     }
   }
@@ -3233,7 +3349,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
     var row = [];
     if (typeof(data.getValue(i, 0)) === 'undefined' ||
         data.getValue(i, 0) === null) {
-      Dygraph.warn("Ignoring row " + i +
+      console.warn("Ignoring row " + i +
                    " of DataTable because of undefined or null first column.");
       continue;
     }
@@ -3279,7 +3395,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   }
 
   if (outOfOrder) {
-    Dygraph.warn("DataTable is out of order; order it correctly to speed loading.");
+    console.warn("DataTable is out of order; order it correctly to speed loading.");
     ret.sort(function(a,b) { return a[0] - b[0]; });
   }
   this.rawData_ = ret;
@@ -3291,6 +3407,17 @@ Dygraph.prototype.parseDataTable_ = function(data) {
 };
 
 /**
+ * Signals to plugins that the chart data has updated.
+ * This happens after the data has updated but before the chart has redrawn.
+ */
+Dygraph.prototype.cascadeDataDidUpdateEvent_ = function() {
+  // TODO(danvk): there are some issues checking xAxisRange() and using
+  // toDomCoords from handlers of this event. The visible range should be set
+  // when the chart is drawn, not derived from the data.
+  this.cascadeEvents_('dataDidUpdate', {});
+};
+
+/**
  * Get the CSV data. If it's in a function, call that function. If it's in a
  * file, do an XMLHttpRequest to get it.
  * @private
@@ -3305,11 +3432,13 @@ Dygraph.prototype.start_ = function() {
 
   if (Dygraph.isArrayLike(data)) {
     this.rawData_ = this.parseArray_(data);
+    this.cascadeDataDidUpdateEvent_();
     this.predraw_();
   } else if (typeof data == 'object' &&
              typeof data.getColumnRange == 'function') {
     // must be a DataTable from gviz.
     this.parseDataTable_(data);
+    this.cascadeDataDidUpdateEvent_();
     this.predraw_();
   } else if (typeof data == 'string') {
     // Heuristic: a newline means it's CSV data. Otherwise it's an URL.
@@ -3341,7 +3470,7 @@ Dygraph.prototype.start_ = function() {
       req.send(null);
     }
   } else {
-    Dygraph.error("Unknown data format: " + (typeof data));
+    console.error("Unknown data format: " + (typeof data));
   }
 };
 
@@ -3399,6 +3528,10 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) {
   this.attributes_.reparseSeries();
 
   if (file) {
+    // This event indicates that the data is about to change, but hasn't yet.
+    // TODO(danvk): support cancelation of the update via this event.
+    this.cascadeEvents_('dataWillUpdate', {});
+
     this.file_ = file;
     if (!block_redraw) this.start_();
   } else {
@@ -3421,6 +3554,7 @@ Dygraph.prototype.updateOptions = function(input_attrs, block_redraw) {
 Dygraph.mapLegacyOptions_ = function(attrs) {
   var my_attrs = {};
   for (var k in attrs) {
+    if (!attrs.hasOwnProperty(k)) continue;
     if (k == 'file') continue;
     if (attrs.hasOwnProperty(k)) my_attrs[k] = attrs[k];
   }
@@ -3432,7 +3566,7 @@ Dygraph.mapLegacyOptions_ = function(attrs) {
   };
   var map = function(opt, axis, new_opt) {
     if (typeof(attrs[opt]) != 'undefined') {
-      Dygraph.warn("Option " + opt + " is deprecated. Use the " +
+      console.warn("Option " + opt + " is deprecated. Use the " +
           new_opt + " option for the " + axis + " axis instead. " +
           "(e.g. { axes : { " + axis + " : { " + new_opt + " : ... } } } " +
           "(see http://dygraphs.com/per-axis.html for more information.");
@@ -3475,7 +3609,7 @@ Dygraph.prototype.resize = function(width, height) {
   this.resize_lock = true;
 
   if ((width === null) != (height === null)) {
-    Dygraph.warn("Dygraph.resize() should be called with zero parameters or " +
+    console.warn("Dygraph.resize() should be called with zero parameters or " +
                  "two non-NULL parameters. Pretending it was zero.");
     width = height = null;
   }
@@ -3538,7 +3672,7 @@ Dygraph.prototype.visibility = function() {
 Dygraph.prototype.setVisibility = function(num, value) {
   var x = this.visibility();
   if (num < 0 || num >= x.length) {
-    Dygraph.warn("invalid series number in setVisibility: " + num);
+    console.warn("invalid series number in setVisibility: " + num);
   } else {
     x[num] = value;
     this.predraw_();
@@ -3566,7 +3700,7 @@ Dygraph.prototype.setAnnotations = function(ann, suppressDraw) {
   Dygraph.addAnnotationRule();
   this.annotations_ = ann;
   if (!this.layout_) {
-    Dygraph.warn("Tried to setAnnotations before dygraph was ready. " +
+    console.warn("Tried to setAnnotations before dygraph was ready. " +
                  "Try setting them in a ready() block. See " +
                  "dygraphs.com/tests/annotation.html");
     return;
@@ -3620,7 +3754,7 @@ Dygraph.prototype.ready = function(callback) {
   if (this.is_initial_draw_) {
     this.readyFns_.push(callback);
   } else {
-    callback(this);
+    callback.call(this, this);
   }
 };
 
@@ -3663,5 +3797,9 @@ Dygraph.addAnnotationRule = function() {
     }
   }
 
-  Dygraph.warn("Unable to add default annotation CSS rule; display may be off.");
+  console.warn("Unable to add default annotation CSS rule; display may be off.");
 };
+
+return Dygraph;
+
+})();
diff --git a/extras/smooth-plotter.js b/extras/smooth-plotter.js
new file mode 100644 (file)
index 0000000..119bf3f
--- /dev/null
@@ -0,0 +1,126 @@
+var smoothPlotter = (function() {
+"use strict";
+
+/**
+ * Given three sequential points, p0, p1 and p2, find the left and right
+ * control points for p1.
+ *
+ * The three points are expected to have x and y properties.
+ *
+ * The alpha parameter controls the amount of smoothing.
+ * If Î±=0, then both control points will be the same as p1 (i.e. no smoothing).
+ *
+ * Returns [l1x, l1y, r1x, r1y]
+ *
+ * It's guaranteed that the line from (l1x, l1y)-(r1x, r1y) passes through p1.
+ * Unless allowFalseExtrema is set, then it's also guaranteed that:
+ *   l1y âˆˆ [p0.y, p1.y]
+ *   r1y âˆˆ [p1.y, p2.y]
+ *
+ * The basic algorithm is:
+ * 1. Put the control points l1 and r1 Î± of the way down (p0, p1) and (p1, p2).
+ * 2. Shift l1 and r2 so that the line l1–r1 passes through p1
+ * 3. Adjust to prevent false extrema while keeping p1 on the l1–r1 line.
+ *
+ * This is loosely based on the HighCharts algorithm.
+ */
+function getControlPoints(p0, p1, p2, opt_alpha, opt_allowFalseExtrema) {
+  var alpha = (opt_alpha !== undefined) ? opt_alpha : 1/3;  // 0=no smoothing, 1=crazy smoothing
+  var allowFalseExtrema = opt_allowFalseExtrema || false;
+
+  if (!p2) {
+    return [p1.x, p1.y, null, null];
+  }
+
+  // Step 1: Position the control points along each line segment.
+  var l1x = (1 - alpha) * p1.x + alpha * p0.x,
+      l1y = (1 - alpha) * p1.y + alpha * p0.y,
+      r1x = (1 - alpha) * p1.x + alpha * p2.x,
+      r1y = (1 - alpha) * p1.y + alpha * p2.y;
+
+  // Step 2: shift the points up so that p1 is on the l1–r1 line.
+  if (l1x != r1x) {
+    // This can be derived w/ some basic algebra.
+    var deltaY = p1.y - r1y - (p1.x - r1x) * (l1y - r1y) / (l1x - r1x);
+    l1y += deltaY;
+    r1y += deltaY;
+  }
+
+  // Step 3: correct to avoid false extrema.
+  if (!allowFalseExtrema) {
+    if (l1y > p0.y && l1y > p1.y) {
+      l1y = Math.max(p0.y, p1.y);
+      r1y = 2 * p1.y - l1y;
+    } else if (l1y < p0.y && l1y < p1.y) {
+      l1y = Math.min(p0.y, p1.y);
+      r1y = 2 * p1.y - l1y;
+    }
+
+    if (r1y > p1.y && r1y > p2.y) {
+      r1y = Math.max(p1.y, p2.y);
+      l1y = 2 * p1.y - r1y;
+    } else if (r1y < p1.y && r1y < p2.y) {
+      r1y = Math.min(p1.y, p2.y);
+      l1y = 2 * p1.y - r1y;
+    }
+  }
+
+  return [l1x, l1y, r1x, r1y];
+}
+
+
+// A plotter which uses splines to create a smooth curve.
+// See tests/plotters.html for a demo.
+// Can be controlled via smoothPlotter.smoothing
+function smoothPlotter(e) {
+  var ctx = e.drawingContext,
+      points = e.points;
+
+  ctx.beginPath();
+  ctx.moveTo(points[0].canvasx, points[0].canvasy);
+
+  // right control point for previous point
+  var lastRightX = points[0].canvasx, lastRightY = points[0].canvasy;
+  var isOK = Dygraph.isOK;  // i.e. is none of (null, undefined, NaN)
+
+  for (var i = 1; i < points.length; i++) {
+    var p0 = points[i - 1],
+        p1 = points[i],
+        p2 = points[i + 1];
+    p0 = p0 && isOK(p0.canvasy) ? p0 : null;
+    p1 = p1 && isOK(p1.canvasy) ? p1 : null;
+    p2 = p2 && isOK(p2.canvasy) ? p2 : null;
+    if (p0 && p1) {
+      var controls = getControlPoints({x: p0.canvasx, y: p0.canvasy},
+                                      {x: p1.canvasx, y: p1.canvasy},
+                                      p2 && {x: p2.canvasx, y: p2.canvasy},
+                                      smoothPlotter.smoothing);
+      // Uncomment to show the control points:
+      // ctx.lineTo(lastRightX, lastRightY);
+      // ctx.lineTo(controls[0], controls[1]);
+      // ctx.lineTo(p1.canvasx, p1.canvasy);
+      lastRightX = (lastRightX !== null) ? lastRightX : p0.canvasx;
+      lastRightY = (lastRightY !== null) ? lastRightY : p0.canvasy;
+      ctx.bezierCurveTo(lastRightX, lastRightY,
+                        controls[0], controls[1],
+                        p1.canvasx, p1.canvasy);
+      lastRightX = controls[2];
+      lastRightY = controls[3];
+    } else if (p1) {
+      // We're starting again after a missing point.
+      ctx.moveTo(p1.canvasx, p1.canvasy);
+      lastRightX = p1.canvasx;
+      lastRightY = p1.canvasy;
+    } else {
+      lastRightX = lastRightY = null;
+    }
+  }
+
+  ctx.stroke();
+}
+smoothPlotter.smoothing = 1/3;
+smoothPlotter._getControlPoints = getControlPoints;  // for testing
+
+return smoothPlotter;
+
+})();
diff --git a/file-size-stats.sh b/file-size-stats.sh
deleted file mode 100755 (executable)
index fb81519..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/bin/bash
-
-# Reports compressed file sizes for each JS file in dygraphs.
-
-# This list needs to be kept in sync w/ the one in dygraph-dev.js
-# and the one in jsTestDriver.conf.
-for file in \
-dygraph-layout.js \
-dygraph-canvas.js \
-dygraph.js \
-dygraph-utils.js \
-dygraph-gviz.js \
-dygraph-interaction-model.js \
-dygraph-tickers.js \
-dashed-canvas.js \
-dygraph-plugin-base.js \
-plugins/annotations.js \
-plugins/axes.js \
-plugins/range-selector.js \
-plugins/chart-labels.js \
-plugins/grid.js \
-plugins/legend.js \
-dygraph-plugin-install.js \
-; do
-  base_size=$(cat $file | wc -c)
-  cat $file \
-    | perl -ne 'print unless m,REMOVE_FOR_COMBINED,..m,/REMOVE_FOR_COMBINED,' \
-    > /tmp/dygraph.js
-  min_size=$(java -jar yuicompressor-2.4.2.jar /tmp/dygraph.js | gzip -c | wc -c)
-
-  echo "$min_size ($base_size) $file"
-done
index 58ab0d7..a224156 100755 (executable)
@@ -5,6 +5,7 @@ GetSources () {
   # This list needs to be kept in sync w/ the one in dygraph-dev.js
   # and the one in jsTestDriver.conf. Order matters, except for the plugins.
   for F in \
+    polyfills/console.js \
     dashed-canvas.js \
     dygraph-options.js \
     dygraph-layout.js \
@@ -32,18 +33,21 @@ GetSources () {
 # Pack all the JS together.
 CatSources () {
   GetSources \
-  | xargs cat \
-  | perl -ne 'print unless m,REMOVE_FOR_COMBINED,..m,/REMOVE_FOR_COMBINED,'
+  | xargs cat 
 }
 
 Copyright () {
-  echo '/*! @license Copyright 2011 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */'
+  echo '/*! @license Copyright 2014 Dan Vanderkam (danvdk@gmail.com) MIT-licensed (http://opensource.org/licenses/MIT) */'
 }
 
 CatCompressed () {
-  Copyright
-  CatSources \
-  | java -jar yuicompressor-2.4.2.jar --type js
+  node_modules/uglify-js/bin/uglifyjs \
+    $(GetSources | xargs) \
+    --compress warnings=false \
+    --mangle \
+    --define DEBUG=false \
+    --preamble "$(Copyright)" \
+    $*
 }
 
 ACTION="${1:-update}"
@@ -59,8 +63,9 @@ compress*|cat_compress*)
   CatCompressed
   ;;
 update)
-  CatCompressed > dygraph-combined.js
-  chmod a+r dygraph-combined.js
+  CatCompressed --source-map dygraph-combined.js.map \
+    > dygraph-combined.js
+  chmod a+r dygraph-combined.js dygraph-combined.js.map
   ;;
 *)
   echo >&2 "Unknown action '$ACTION'"
index f6d7d79..d73fcc4 100755 (executable)
@@ -204,6 +204,7 @@ Some callbacks take a point argument. Its properties are:<br/>
 <li>xval/yval: The data coordinates of the point (with dates/times as millis since epoch)</li>
 <li>canvasx/canvasy: The canvas coordinates at which the point is drawn.</li>
 <li>name: The name of the data series to which the point belongs</li>
+<li>idx: The row number of the point in the data set</li>
 </ul>
 </div> <!-- #content -->
 
index a6157d6..4c32223 100755 (executable)
@@ -49,6 +49,20 @@ print '''
 %(current_html)s
 </div>
 
+<p>There's a hosted version of dygraphs on <a href="https://cdnjs.com/libraries/dygraph">cdnjs.com</a>:</p>
+
+<pre>&lt;script src="//cdnjs.cloudflare.com/ajax/libs/dygraph/%(version)s/dygraph-combined.js"&gt;&lt;/script&gt;</pre>
+
+<p>You can install dygraphs using <a href="https://www.npmjs.org/package/dygraphs">NPM</a> or <a href="http://bower.io/search/?q=dygraphs">Bower</a>.</p>
+
+<p>To install using NPM:</p>
+<pre>$ npm install dygraphs
+# dygraphs is now in node_modules/dygraphs/dygraph-combined.js</pre>
+
+<p>To install using bower:</p>
+<pre>$ bower install dygraphs
+# dygraphs is now in bower_components/dygraphs/dygraph-combined.js</pre>
+
 <p>For dev (non-minified) JS, see <a href="https://github.com/danvk/dygraphs/blob/master/dygraph-dev.js">dygraph-dev.js</a> on <a href="https://github.com/danvk/dygraphs/">github</a>.</a>
 
 <p>To generate your own minified JS, run:</p>
diff --git a/jshint/CHANGELOG b/jshint/CHANGELOG
deleted file mode 100644 (file)
index 695f0ad..0000000
+++ /dev/null
@@ -1,63 +0,0 @@
-April 16, 2011
-  * New edition: 2011-04-16
-
-  * Unit tests for all options and some core functions
-  * A number of small typo and message fixes
-  * JSHint is now a JavaScript/JSON parser only
-    (ADSafe, HTML, CSS related code was removed)
-  * JSHint now supports function-scoped options
-  * JSHint now supports both /*jshint and /*jslint
-
-  * JSHint now supports shebangs (#!)
-  * Added support for typed array globals
-  * Allowed the use of variables/functions before definition.
-    (option 'latedef' to disallow)
-  * Fixed a bug with 'forin' option
-  * Fixed Rhino wrapper's CLI options support
-  * Fixed a bug with JSHint leaking internal variables
-  * Added option 'expr' to allow ExpressionStatement as valid Program
-  * Added an option 'prototypejs' to pre-define Prototype globals
-  * Added XPathResult et al. to the 'browser' option
-  * Added support for 'undefined' as a function parameter
-  * Option 'boss' has now precedence over 'eqeqeq' when it comes to '== null'
-  * Fixed a bug with JSHint parsing getters/setters as function statements
-  * Added an option 'mootools' to pre-define MooTools globals
-  * Added an option 'globalstrict' to allow the use of global strict mode
-  * Added HTMLElement to the browser environment
-  * Added support for the void operator
-  * Fixed a bug with 'new Array'
-  * Added option 'white' to check for trailing whitespaces
-
-March 01, 2011
-  * New edition: 2011-03-02
-
-  * When library is used from Rhino, you can provide options via command line arguments
-
-  * Added new HTML5 globals to the 'browser' option
-  * Tolerate == null when boss:true
-  * Tolerate undefined variables in the typeof and delete
-  * Tolerate undefined as a formal parameter
-  * Recognize new Array(<expr>) as a valid expression
-  * Added support for explicit case statement fallthroughs (using special comments)
-  * Added third formal parameter to JSHINT for specifying pre-defined globals
-
-  * New option 'asi' to tolerate the use of automatic semicolon insertion
-  * New option 'jquery' to assume jQuery environment
-  * New option 'couch' to assume CouchDB environment
-
-February 19, 2011
-  * New edition: 2011-02-19
-
-  * Library can act as a Node.js module and a Rhino program
-
-  * Tolerate single statements in if/for/while constructions ('curly' to disallow)
-  * Tolerate arguments.callee and arguments.caller ('noarg' to disallow)
-  * Tolerate empty blocks ('noempty' to disallow)
-  * Tolerate the use of `new` for side-effects ('nonew' to disallow)
-  * Less strict styling check by default ('white' to revert)
-
-  * New option 'boss' to tolerate assignments inside if/for/while
-  * New option 'node' to assume Node environment
-
-January 19, 2011
-  * Forked JSLint from the edition 2010-12-16
\ No newline at end of file
diff --git a/jshint/Makefile b/jshint/Makefile
deleted file mode 100644 (file)
index 512ca3b..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-build_dir:
-       @mkdir -p "build"
-
-rhino: build_dir
-       @echo "Building JSHint for Rhino"
-       @cat "jshint.js" > "build/jshint-rhino.js" && \
-               cat "env/rhino.js" >> "build/jshint-rhino.js" && \
-               echo "Done"
-
-test:
-       @echo "Running all tests"
-       @expresso tests/*.js
-
-clean:
-       @echo "Cleaning"
-       @rm -f build/*.js && echo "Done"
diff --git a/jshint/README.markdown b/jshint/README.markdown
deleted file mode 100755 (executable)
index d8e6713..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-JSHint, A Static Code Analysis Tool for JavaScript
-==================================================
-
-JSHint is a community-driven tool to detect errors and potential problems in
-JavaScript code and to enforce your team's coding conventions.
-
-**IMPORTANT**:
-
- * This README is for people who are thinking about contributing to JSHint. For general usage
-   please refer to [our website](http://jshint.com/).
- * If you want to report a bug about the website, please go to the
-   [jshint/site](https://github.com/jshint/site/) repository.
- * If you want to report a bug or contribute to our NPM package, please go to the
-   [jshint/node-jshint](https://github.com/jshint/node-jshint/) repository.
-
-Reporting a bug
----------------
-
-To report a bug simply create a [new GitHub Issue](https://github.com/jshint/jshint/issues/new) and
-describe your problem or suggestion. We welcome all kind of feedback regarding JSHint including but
-not limited to:
-
- * When JSHint doesn't work as expected
- * When JSHint complains about valid JavaScript code that works in all browsers
- * When you simply want a new option or feature
-
-Please, before reporting a bug look around to see if there are any open or closed tickets that
-cover your issue. And remember the wisdom: pull request > bug report > tweet.
-
-Submitting patches
-------------------
-
-The best way to make sure your issue is addressed is to submit a patch. GitHub provides a very
-nice interface--pull requests--for that but we accept patches through all mediums: email, issue
-comment, tweet with a link to a snippet, etc.
-
-Before submitting a patch make sure that you comply to our style. We don't have specific style
-guide so just look around the code you are changing.
-
-Also, make sure that you write tests for new features and make sure that all tests pass before
-submitting a patch. Patches that break the build will be rejected.
-
-**FEATURE FREEZE**: Please note that we currently have a feature freeze on new environments and
-styling options. The only patches we accept at this time are for bug fixes.
-
-Tests
------
-
-To run tests you will need to install [node.js](http://nodejs.org/) and
-expresso. You can install the latter with npm:
-
-    npm install expresso
-
-After that, running tests is as easy as:
-
-    expresso tests/*.js
-
-Attribution
------------
-
-Maintainer: [Anton Kovalyov](http://anton.kovalyov.net/) ([@valueof](http://twitter.com/valueof))
-
-Distinguished Contributors:
-
- * [Wolfgang Kluge](http://klugesoftware.de/) ([blog](http://gehirnwindung.de/))
- * [Josh Perez](http://www.goatslacker.com/)
-
-Thank you!
-----------
-
-We really appreciate all kind of feedback and contributions. Thanks for using and supporing JSHint!
\ No newline at end of file
diff --git a/jshint/build/jshint-rhino.js b/jshint/build/jshint-rhino.js
deleted file mode 100644 (file)
index 055d38c..0000000
+++ /dev/null
@@ -1,4389 +0,0 @@
-/*!
- * JSHint, by JSHint Community.
- *
- * Licensed under the same slightly modified MIT license that JSLint is.
- * It stops evil-doers everywhere.
- *
- * JSHint is a derivative work of JSLint:
- *
- *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
- *
- *   Permission is hereby granted, free of charge, to any person obtaining
- *   a copy of this software and associated documentation files (the "Software"),
- *   to deal in the Software without restriction, including without limitation
- *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
- *   and/or sell copies of the Software, and to permit persons to whom
- *   the Software is furnished to do so, subject to the following conditions:
- *
- *   The above copyright notice and this permission notice shall be included
- *   in all copies or substantial portions of the Software.
- *
- *   The Software shall be used for Good, not Evil.
- *
- *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- *   DEALINGS IN THE SOFTWARE.
- *
- * JSHint was forked from 2010-12-16 edition of JSLint.
- *
- */
-
-/*
- JSHINT is a global function. It takes two parameters.
-
-     var myResult = JSHINT(source, option);
-
- The first parameter is either a string or an array of strings. If it is a
- string, it will be split on '\n' or '\r'. If it is an array of strings, it
- is assumed that each string represents one line. The source can be a
- JavaScript text or a JSON text.
-
- The second parameter is an optional object of options which control the
- operation of JSHINT. Most of the options are booleans: They are all
- optional and have a default value of false. One of the options, predef,
- can be an array of names, which will be used to declare global variables,
- or an object whose keys are used as global names, with a boolean value
- that determines if they are assignable.
-
- If it checks out, JSHINT returns true. Otherwise, it returns false.
-
- If false, you can inspect JSHINT.errors to find out the problems.
- JSHINT.errors is an array of objects containing these members:
-
- {
-     line      : The line (relative to 0) at which the lint was found
-     character : The character (relative to 0) at which the lint was found
-     reason    : The problem
-     evidence  : The text line in which the problem occurred
-     raw       : The raw message before the details were inserted
-     a         : The first detail
-     b         : The second detail
-     c         : The third detail
-     d         : The fourth detail
- }
-
- If a fatal error was found, a null will be the last element of the
- JSHINT.errors array.
-
- You can request a Function Report, which shows all of the functions
- and the parameters and vars that they use. This can be used to find
- implied global variables and other problems. The report is in HTML and
- can be inserted in an HTML <body>.
-
-     var myReport = JSHINT.report(limited);
-
- If limited is true, then the report will be limited to only errors.
-
- You can request a data structure which contains JSHint's results.
-
-     var myData = JSHINT.data();
-
- It returns a structure with this form:
-
- {
-     errors: [
-         {
-             line: NUMBER,
-             character: NUMBER,
-             reason: STRING,
-             evidence: STRING
-         }
-     ],
-     functions: [
-         name: STRING,
-         line: NUMBER,
-         last: NUMBER,
-         param: [
-             STRING
-         ],
-         closure: [
-             STRING
-         ],
-         var: [
-             STRING
-         ],
-         exception: [
-             STRING
-         ],
-         outer: [
-             STRING
-         ],
-         unused: [
-             STRING
-         ],
-         global: [
-             STRING
-         ],
-         label: [
-             STRING
-         ]
-     ],
-     globals: [
-         STRING
-     ],
-     member: {
-         STRING: NUMBER
-     },
-     unuseds: [
-         {
-             name: STRING,
-             line: NUMBER
-         }
-     ],
-     implieds: [
-         {
-             name: STRING,
-             line: NUMBER
-         }
-     ],
-     urls: [
-         STRING
-     ],
-     json: BOOLEAN
- }
-
- Empty arrays will not be included.
-
-*/
-
-/*jshint
- evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true,
- undef: true, maxlen: 100, indent:4
-*/
-
-/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)",
- "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)",
- "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
- "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==",
- "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax,
- __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio,
- Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas,
- CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date,
- Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag,
- E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event,
- Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form,
- FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey,
- HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement,
- HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement,
- HTMLDivElement, HTMLDListElement, HTMLFieldSetElement,
- HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement,
- HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement,
- HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement,
- HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement,
- HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement,
- HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement,
- HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement,
- HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement,
- HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement,
- HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement,
- HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement,
- Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array,
- Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E,
- MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native,
- NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI,
- POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError,
- Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation,
- SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion,
- ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller,
- Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables,
- SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template,
- Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL,
- VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator,
- XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a,
- addEventListener, address, alert, apply, applicationCache, arguments, arity,
- asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee,
- caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout,
- close, closed, closure, comment, condition, confirm, console, constructor,
- content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI,
- decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document,
- dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent,
- entityify, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil,
- ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus,
- forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions,
- g, gc, getComputedStyle, getRow, getter, GLOBAL, global, globals, globalstrict,
- hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include,
- indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray,
- isDigit, isFinite, isNaN, iterator, java, join, jshint,
- JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak,
- latedef, lbp, led, left, length, line, load, loadClass, localStorage, location,
- log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy,
- moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen,
- nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus,
- onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param,
- parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt,
- proto, prototype, prototypejs, provides, push, quit, range, raw, reach, reason, regexp,
- readFile, readUrl, regexdash, removeEventListener, replace, report, require,
- reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right,
- runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal,
- send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice,
- smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow,
- supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing,
- type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis,
- value, valueOf, var, version, WebSocket, white, window, Worker, wsh*/
-
-/*global exports: false */
-
-// We build the application inside a function so that we produce only a single
-// global variable. That function will be invoked immediately, and its return
-// value is the JSHINT function itself.
-
-var JSHINT = (function () {
-    "use strict";
-
-    var anonname,       // The guessed name for anonymous functions.
-
-// These are operators that should not be used with the ! operator.
-
-        bang = {
-            '<'  : true,
-            '<=' : true,
-            '==' : true,
-            '===': true,
-            '!==': true,
-            '!=' : true,
-            '>'  : true,
-            '>=' : true,
-            '+'  : true,
-            '-'  : true,
-            '*'  : true,
-            '/'  : true,
-            '%'  : true
-        },
-
-        // These are the JSHint boolean options.
-        boolOptions = {
-            asi         : true, // if automatic semicolon insertion should be tolerated
-            bitwise     : true, // if bitwise operators should not be allowed
-            boss        : true, // if advanced usage of assignments should be allowed
-            browser     : true, // if the standard browser globals should be predefined
-            couch       : true, // if CouchDB globals should be predefined
-            curly       : true, // if curly braces around all blocks should be required
-            debug       : true, // if debugger statements should be allowed
-            devel       : true, // if logging globals should be predefined (console,
-                                // alert, etc.)
-            dojo        : true, // if Dojo Toolkit globals should be predefined
-            eqeqeq      : true, // if === should be required
-            eqnull      : true, // if == null comparisons should be tolerated
-            es5         : true, // if ES5 syntax should be allowed
-            esnext      : true, // if es.next specific syntax should be allowed
-            evil        : true, // if eval should be allowed
-            expr        : true, // if ExpressionStatement should be allowed as Programs
-            forin       : true, // if for in statements must filter
-            funcscope   : true, // if only function scope should be used for scope tests
-            globalstrict: true, // if global "use strict"; should be allowed (also
-                                // enables 'strict')
-            immed       : true, // if immediate invocations must be wrapped in parens
-            iterator    : true, // if the `__iterator__` property should be allowed
-            jquery      : true, // if jQuery globals should be predefined
-            lastsemic   : true, // if semicolons may be ommitted for the trailing
-                                // statements inside of a one-line blocks.
-            latedef     : true, // if the use before definition should not be tolerated
-            laxbreak    : true, // if line breaks should not be checked
-            loopfunc    : true, // if functions should be allowed to be defined within
-                                // loops
-            mootools    : true, // if MooTools globals should be predefined
-            multistr    : true, // allow multiline strings
-            newcap      : true, // if constructor names must be capitalized
-            noarg       : true, // if arguments.caller and arguments.callee should be
-                                // disallowed
-            node        : true, // if the Node.js environment globals should be
-                                // predefined
-            noempty     : true, // if empty blocks should be disallowed
-            nonew       : true, // if using `new` for side-effects should be disallowed
-            nonstandard : true, // if non-standard (but widely adopted) globals should
-                                // be predefined
-            nomen       : true, // if names should be checked
-            onevar      : true, // if only one var statement per function should be
-                                // allowed
-            onecase     : true, // if one case switch statements should be allowed
-            passfail    : true, // if the scan should stop on first error
-            plusplus    : true, // if increment/decrement should not be allowed
-            proto       : true, // if the `__proto__` property should be allowed
-            prototypejs : true, // if Prototype and Scriptaculous globals should be
-                                // predefined
-            regexdash   : true, // if unescaped first/last dash (-) inside brackets
-                                // should be tolerated
-            regexp      : true, // if the . should not be allowed in regexp literals
-            rhino       : true, // if the Rhino environment globals should be predefined
-            undef       : true, // if variables should be declared before used
-            scripturl   : true, // if script-targeted URLs should be tolerated
-            shadow      : true, // if variable shadowing should be tolerated
-            smarttabs   : true, // if smarttabs should be tolerated
-                                // (http://www.emacswiki.org/emacs/SmartTabs)
-            strict      : true, // require the "use strict"; pragma
-            sub         : true, // if all forms of subscript notation are tolerated
-            supernew    : true, // if `new function () { ... };` and `new Object;`
-                                // should be tolerated
-            trailing    : true, // if trailing whitespace rules apply
-            validthis   : true, // if 'this' inside a non-constructor function is valid.
-                                // This is a function scoped option only.
-            white       : true, // if strict whitespace rules apply
-            wsh         : true  // if the Windows Scripting Host environment globals
-                                // should be predefined
-        },
-
-        // browser contains a set of global names which are commonly provided by a
-        // web browser environment.
-        browser = {
-            ArrayBuffer              :  false,
-            ArrayBufferView          :  false,
-            Audio                    :  false,
-            addEventListener         :  false,
-            applicationCache         :  false,
-            blur                     :  false,
-            clearInterval            :  false,
-            clearTimeout             :  false,
-            close                    :  false,
-            closed                   :  false,
-            DataView                 :  false,
-            defaultStatus            :  false,
-            document                 :  false,
-            event                    :  false,
-            FileReader               :  false,
-            Float32Array             :  false,
-            Float64Array             :  false,
-            FormData                 :  false,
-            focus                    :  false,
-            frames                   :  false,
-            getComputedStyle         :  false,
-            HTMLElement              :  false,
-            HTMLAnchorElement        :  false,
-            HTMLBaseElement          :  false,
-            HTMLBlockquoteElement    :  false,
-            HTMLBodyElement          :  false,
-            HTMLBRElement            :  false,
-            HTMLButtonElement        :  false,
-            HTMLCanvasElement        :  false,
-            HTMLDirectoryElement     :  false,
-            HTMLDivElement           :  false,
-            HTMLDListElement         :  false,
-            HTMLFieldSetElement      :  false,
-            HTMLFontElement          :  false,
-            HTMLFormElement          :  false,
-            HTMLFrameElement         :  false,
-            HTMLFrameSetElement      :  false,
-            HTMLHeadElement          :  false,
-            HTMLHeadingElement       :  false,
-            HTMLHRElement            :  false,
-            HTMLHtmlElement          :  false,
-            HTMLIFrameElement        :  false,
-            HTMLImageElement         :  false,
-            HTMLInputElement         :  false,
-            HTMLIsIndexElement       :  false,
-            HTMLLabelElement         :  false,
-            HTMLLayerElement         :  false,
-            HTMLLegendElement        :  false,
-            HTMLLIElement            :  false,
-            HTMLLinkElement          :  false,
-            HTMLMapElement           :  false,
-            HTMLMenuElement          :  false,
-            HTMLMetaElement          :  false,
-            HTMLModElement           :  false,
-            HTMLObjectElement        :  false,
-            HTMLOListElement         :  false,
-            HTMLOptGroupElement      :  false,
-            HTMLOptionElement        :  false,
-            HTMLParagraphElement     :  false,
-            HTMLParamElement         :  false,
-            HTMLPreElement           :  false,
-            HTMLQuoteElement         :  false,
-            HTMLScriptElement        :  false,
-            HTMLSelectElement        :  false,
-            HTMLStyleElement         :  false,
-            HTMLTableCaptionElement  :  false,
-            HTMLTableCellElement     :  false,
-            HTMLTableColElement      :  false,
-            HTMLTableElement         :  false,
-            HTMLTableRowElement      :  false,
-            HTMLTableSectionElement  :  false,
-            HTMLTextAreaElement      :  false,
-            HTMLTitleElement         :  false,
-            HTMLUListElement         :  false,
-            HTMLVideoElement         :  false,
-            history                  :  false,
-            Int16Array               :  false,
-            Int32Array               :  false,
-            Int8Array                :  false,
-            Image                    :  false,
-            length                   :  false,
-            localStorage             :  false,
-            location                 :  false,
-            moveBy                   :  false,
-            moveTo                   :  false,
-            name                     :  false,
-            navigator                :  false,
-            onbeforeunload           :  true,
-            onblur                   :  true,
-            onerror                  :  true,
-            onfocus                  :  true,
-            onload                   :  true,
-            onresize                 :  true,
-            onunload                 :  true,
-            open                     :  false,
-            openDatabase             :  false,
-            opener                   :  false,
-            Option                   :  false,
-            parent                   :  false,
-            print                    :  false,
-            removeEventListener      :  false,
-            resizeBy                 :  false,
-            resizeTo                 :  false,
-            screen                   :  false,
-            scroll                   :  false,
-            scrollBy                 :  false,
-            scrollTo                 :  false,
-            sessionStorage           :  false,
-            setInterval              :  false,
-            setTimeout               :  false,
-            SharedWorker             :  false,
-            status                   :  false,
-            top                      :  false,
-            Uint16Array              :  false,
-            Uint32Array              :  false,
-            Uint8Array               :  false,
-            WebSocket                :  false,
-            window                   :  false,
-            Worker                   :  false,
-            XMLHttpRequest           :  false,
-            XPathEvaluator           :  false,
-            XPathException           :  false,
-            XPathExpression          :  false,
-            XPathNamespace           :  false,
-            XPathNSResolver          :  false,
-            XPathResult              :  false
-        },
-
-        couch = {
-            "require" : false,
-            respond   : false,
-            getRow    : false,
-            emit      : false,
-            send      : false,
-            start     : false,
-            sum       : false,
-            log       : false,
-            exports   : false,
-            module    : false,
-            provides  : false
-        },
-
-        devel = {
-            alert   : false,
-            confirm : false,
-            console : false,
-            Debug   : false,
-            opera   : false,
-            prompt  : false
-        },
-
-        dojo = {
-            dojo      : false,
-            dijit     : false,
-            dojox     : false,
-            define    : false,
-            "require" : false
-        },
-
-        escapes = {
-            '\b': '\\b',
-            '\t': '\\t',
-            '\n': '\\n',
-            '\f': '\\f',
-            '\r': '\\r',
-            '"' : '\\"',
-            '/' : '\\/',
-            '\\': '\\\\'
-        },
-
-        funct,          // The current function
-
-        functionicity = [
-            'closure', 'exception', 'global', 'label',
-            'outer', 'unused', 'var'
-        ],
-
-        functions,      // All of the functions
-
-        global,         // The global scope
-        implied,        // Implied globals
-        inblock,
-        indent,
-        jsonmode,
-
-        jquery = {
-            '$'    : false,
-            jQuery : false
-        },
-
-        lines,
-        lookahead,
-        member,
-        membersOnly,
-
-        mootools = {
-            '$'             : false,
-            '$$'            : false,
-            Assets          : false,
-            Browser         : false,
-            Chain           : false,
-            Class           : false,
-            Color           : false,
-            Cookie          : false,
-            Core            : false,
-            Document        : false,
-            DomReady        : false,
-            DOMReady        : false,
-            Drag            : false,
-            Element         : false,
-            Elements        : false,
-            Event           : false,
-            Events          : false,
-            Fx              : false,
-            Group           : false,
-            Hash            : false,
-            HtmlTable       : false,
-            Iframe          : false,
-            IframeShim      : false,
-            InputValidator  : false,
-            instanceOf      : false,
-            Keyboard        : false,
-            Locale          : false,
-            Mask            : false,
-            MooTools        : false,
-            Native          : false,
-            Options         : false,
-            OverText        : false,
-            Request         : false,
-            Scroller        : false,
-            Slick           : false,
-            Slider          : false,
-            Sortables       : false,
-            Spinner         : false,
-            Swiff           : false,
-            Tips            : false,
-            Type            : false,
-            typeOf          : false,
-            URI             : false,
-            Window          : false
-        },
-
-        nexttoken,
-
-        node = {
-            __filename    : false,
-            __dirname     : false,
-            Buffer        : false,
-            console       : false,
-            exports       : false,
-            GLOBAL        : false,
-            global        : false,
-            module        : false,
-            process       : false,
-            require       : false,
-            setTimeout    : false,
-            clearTimeout  : false,
-            setInterval   : false,
-            clearInterval : false
-        },
-
-        noreach,
-        option,
-        predefined,     // Global variables defined by option
-        prereg,
-        prevtoken,
-
-        prototypejs = {
-            '$'               : false,
-            '$$'              : false,
-            '$A'              : false,
-            '$F'              : false,
-            '$H'              : false,
-            '$R'              : false,
-            '$break'          : false,
-            '$continue'       : false,
-            '$w'              : false,
-            Abstract          : false,
-            Ajax              : false,
-            Class             : false,
-            Enumerable        : false,
-            Element           : false,
-            Event             : false,
-            Field             : false,
-            Form              : false,
-            Hash              : false,
-            Insertion         : false,
-            ObjectRange       : false,
-            PeriodicalExecuter: false,
-            Position          : false,
-            Prototype         : false,
-            Selector          : false,
-            Template          : false,
-            Toggle            : false,
-            Try               : false,
-            Autocompleter     : false,
-            Builder           : false,
-            Control           : false,
-            Draggable         : false,
-            Draggables        : false,
-            Droppables        : false,
-            Effect            : false,
-            Sortable          : false,
-            SortableObserver  : false,
-            Sound             : false,
-            Scriptaculous     : false
-        },
-
-        rhino = {
-            defineClass  : false,
-            deserialize  : false,
-            gc           : false,
-            help         : false,
-            importPackage: false,
-            "java"       : false,
-            load         : false,
-            loadClass    : false,
-            print        : false,
-            quit         : false,
-            readFile     : false,
-            readUrl      : false,
-            runCommand   : false,
-            seal         : false,
-            serialize    : false,
-            spawn        : false,
-            sync         : false,
-            toint32      : false,
-            version      : false
-        },
-
-        scope,      // The current scope
-        stack,
-
-        // standard contains the global names that are provided by the
-        // ECMAScript standard.
-        standard = {
-            Array               : false,
-            Boolean             : false,
-            Date                : false,
-            decodeURI           : false,
-            decodeURIComponent  : false,
-            encodeURI           : false,
-            encodeURIComponent  : false,
-            Error               : false,
-            'eval'              : false,
-            EvalError           : false,
-            Function            : false,
-            hasOwnProperty      : false,
-            isFinite            : false,
-            isNaN               : false,
-            JSON                : false,
-            Math                : false,
-            Number              : false,
-            Object              : false,
-            parseInt            : false,
-            parseFloat          : false,
-            RangeError          : false,
-            ReferenceError      : false,
-            RegExp              : false,
-            String              : false,
-            SyntaxError         : false,
-            TypeError           : false,
-            URIError            : false
-        },
-
-        // widely adopted global names that are not part of ECMAScript standard
-        nonstandard = {
-            escape              : false,
-            unescape            : false
-        },
-
-        standard_member = {
-            E                   : true,
-            LN2                 : true,
-            LN10                : true,
-            LOG2E               : true,
-            LOG10E              : true,
-            MAX_VALUE           : true,
-            MIN_VALUE           : true,
-            NEGATIVE_INFINITY   : true,
-            PI                  : true,
-            POSITIVE_INFINITY   : true,
-            SQRT1_2             : true,
-            SQRT2               : true
-        },
-
-        directive,
-        syntax = {},
-        tab,
-        token,
-        urls,
-        useESNextSyntax,
-        warnings,
-
-        wsh = {
-            ActiveXObject             : true,
-            Enumerator                : true,
-            GetObject                 : true,
-            ScriptEngine              : true,
-            ScriptEngineBuildVersion  : true,
-            ScriptEngineMajorVersion  : true,
-            ScriptEngineMinorVersion  : true,
-            VBArray                   : true,
-            WSH                       : true,
-            WScript                   : true,
-            XDomainRequest            : true
-        };
-
-    // Regular expressions. Some of these are stupidly long.
-    var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
-    (function () {
-        /*jshint maxlen:300 */
-
-        // unsafe comment or string
-        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
-
-        // unsafe characters that are silently deleted by one or more browsers
-        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
-
-        // token
-        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
-
-        // characters in strings that need escapement
-        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
-        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
-
-        // star slash
-        lx = /\*\/|\/\*/;
-
-        // identifier
-        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
-
-        // javascript url
-        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
-
-        // catches /* falls through */ comments
-        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
-    }());
-
-    function F() {}     // Used by Object.create
-
-    function is_own(object, name) {
-
-// The object.hasOwnProperty method fails when the property under consideration
-// is named 'hasOwnProperty'. So we have to use this more convoluted form.
-
-        return Object.prototype.hasOwnProperty.call(object, name);
-    }
-
-// Provide critical ES5 functions to ES3.
-
-    if (typeof Array.isArray !== 'function') {
-        Array.isArray = function (o) {
-            return Object.prototype.toString.apply(o) === '[object Array]';
-        };
-    }
-
-    if (typeof Object.create !== 'function') {
-        Object.create = function (o) {
-            F.prototype = o;
-            return new F();
-        };
-    }
-
-    if (typeof Object.keys !== 'function') {
-        Object.keys = function (o) {
-            var a = [], k;
-            for (k in o) {
-                if (is_own(o, k)) {
-                    a.push(k);
-                }
-            }
-            return a;
-        };
-    }
-
-// Non standard methods
-
-    if (typeof String.prototype.entityify !== 'function') {
-        String.prototype.entityify = function () {
-            return this
-                .replace(/&/g, '&amp;')
-                .replace(/</g, '&lt;')
-                .replace(/>/g, '&gt;');
-        };
-    }
-
-    if (typeof String.prototype.isAlpha !== 'function') {
-        String.prototype.isAlpha = function () {
-            return (this >= 'a' && this <= 'z\uffff') ||
-                (this >= 'A' && this <= 'Z\uffff');
-        };
-    }
-
-    if (typeof String.prototype.isDigit !== 'function') {
-        String.prototype.isDigit = function () {
-            return (this >= '0' && this <= '9');
-        };
-    }
-
-    if (typeof String.prototype.supplant !== 'function') {
-        String.prototype.supplant = function (o) {
-            return this.replace(/\{([^{}]*)\}/g, function (a, b) {
-                var r = o[b];
-                return typeof r === 'string' || typeof r === 'number' ? r : a;
-            });
-        };
-    }
-
-    if (typeof String.prototype.name !== 'function') {
-        String.prototype.name = function () {
-
-// If the string looks like an identifier, then we can return it as is.
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can simply slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe
-// sequences.
-
-            if (ix.test(this)) {
-                return this;
-            }
-            if (nx.test(this)) {
-                return '"' + this.replace(nxg, function (a) {
-                    var c = escapes[a];
-                    if (c) {
-                        return c;
-                    }
-                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
-                }) + '"';
-            }
-            return '"' + this + '"';
-        };
-    }
-
-
-    function combine(t, o) {
-        var n;
-        for (n in o) {
-            if (is_own(o, n)) {
-                t[n] = o[n];
-            }
-        }
-    }
-
-    function assume() {
-        if (option.couch) {
-            combine(predefined, couch);
-        }
-
-        if (option.rhino) {
-            combine(predefined, rhino);
-        }
-
-        if (option.prototypejs) {
-            combine(predefined, prototypejs);
-        }
-
-        if (option.node) {
-            combine(predefined, node);
-        }
-
-        if (option.devel) {
-            combine(predefined, devel);
-        }
-
-        if (option.dojo) {
-            combine(predefined, dojo);
-        }
-
-        if (option.browser) {
-            combine(predefined, browser);
-        }
-
-        if (option.nonstandard) {
-            combine(predefined, nonstandard);
-        }
-
-        if (option.jquery) {
-            combine(predefined, jquery);
-        }
-
-        if (option.mootools) {
-            combine(predefined, mootools);
-        }
-
-        if (option.wsh) {
-            combine(predefined, wsh);
-        }
-
-        if (option.esnext) {
-            useESNextSyntax();
-        }
-
-        if (option.globalstrict && option.strict !== false) {
-            option.strict = true;
-        }
-    }
-
-
-    // Produce an error warning.
-    function quit(message, line, chr) {
-        var percentage = Math.floor((line / lines.length) * 100);
-
-        throw {
-            name: 'JSHintError',
-            line: line,
-            character: chr,
-            message: message + " (" + percentage + "% scanned).",
-            raw: message
-        };
-    }
-
-    function isundef(scope, m, t, a) {
-        return JSHINT.undefs.push([scope, m, t, a]);
-    }
-
-    function warning(m, t, a, b, c, d) {
-        var ch, l, w;
-        t = t || nexttoken;
-        if (t.id === '(end)') {  // `~
-            t = token;
-        }
-        l = t.line || 0;
-        ch = t.from || 0;
-        w = {
-            id: '(error)',
-            raw: m,
-            evidence: lines[l - 1] || '',
-            line: l,
-            character: ch,
-            a: a,
-            b: b,
-            c: c,
-            d: d
-        };
-        w.reason = m.supplant(w);
-        JSHINT.errors.push(w);
-        if (option.passfail) {
-            quit('Stopping. ', l, ch);
-        }
-        warnings += 1;
-        if (warnings >= option.maxerr) {
-            quit("Too many errors.", l, ch);
-        }
-        return w;
-    }
-
-    function warningAt(m, l, ch, a, b, c, d) {
-        return warning(m, {
-            line: l,
-            from: ch
-        }, a, b, c, d);
-    }
-
-    function error(m, t, a, b, c, d) {
-        var w = warning(m, t, a, b, c, d);
-    }
-
-    function errorAt(m, l, ch, a, b, c, d) {
-        return error(m, {
-            line: l,
-            from: ch
-        }, a, b, c, d);
-    }
-
-
-
-// lexical analysis and token construction
-
-    var lex = (function lex() {
-        var character, from, line, s;
-
-// Private lex methods
-
-        function nextLine() {
-            var at,
-                tw; // trailing whitespace check
-
-            if (line >= lines.length)
-                return false;
-
-            character = 1;
-            s = lines[line];
-            line += 1;
-
-            // If smarttabs option is used check for spaces followed by tabs only.
-            // Otherwise check for any occurence of mixed tabs and spaces.
-            if (option.smarttabs)
-                at = s.search(/ \t/);
-            else
-                at = s.search(/ \t|\t /);
-
-            if (at >= 0)
-                warningAt("Mixed spaces and tabs.", line, at + 1);
-
-            s = s.replace(/\t/g, tab);
-            at = s.search(cx);
-
-            if (at >= 0)
-                warningAt("Unsafe character.", line, at);
-
-            if (option.maxlen && option.maxlen < s.length)
-                warningAt("Line too long.", line, s.length);
-
-            // Check for trailing whitespaces
-            tw = /\s+$/.test(s);
-            if (option.trailing && tw && !/^\s+$/.test(s)) {
-                warningAt("Trailing whitespace.", line, tw);
-            }
-            return true;
-        }
-
-// Produce a token object.  The token inherits from a syntax symbol.
-
-        function it(type, value) {
-            var i, t;
-            if (type === '(color)' || type === '(range)') {
-                t = {type: type};
-            } else if (type === '(punctuator)' ||
-                    (type === '(identifier)' && is_own(syntax, value))) {
-                t = syntax[value] || syntax['(error)'];
-            } else {
-                t = syntax[type];
-            }
-            t = Object.create(t);
-            if (type === '(string)' || type === '(range)') {
-                if (!option.scripturl && jx.test(value)) {
-                    warningAt("Script URL.", line, from);
-                }
-            }
-            if (type === '(identifier)') {
-                t.identifier = true;
-                if (value === '__proto__' && !option.proto) {
-                    warningAt("The '{a}' property is deprecated.",
-                        line, from, value);
-                } else if (value === '__iterator__' && !option.iterator) {
-                    warningAt("'{a}' is only available in JavaScript 1.7.",
-                        line, from, value);
-                } else if (option.nomen && (value.charAt(0) === '_' ||
-                         value.charAt(value.length - 1) === '_')) {
-                    if (!option.node || token.id === '.' ||
-                            (value !== '__dirname' && value !== '__filename')) {
-                        warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value);
-                    }
-                }
-            }
-            t.value = value;
-            t.line = line;
-            t.character = character;
-            t.from = from;
-            i = t.id;
-            if (i !== '(endline)') {
-                prereg = i &&
-                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
-                    i === 'return' ||
-                    i === 'case');
-            }
-            return t;
-        }
-
-        // Public lex methods
-        return {
-            init: function (source) {
-                if (typeof source === 'string') {
-                    lines = source
-                        .replace(/\r\n/g, '\n')
-                        .replace(/\r/g, '\n')
-                        .split('\n');
-                } else {
-                    lines = source;
-                }
-
-                // If the first line is a shebang (#!), make it a blank and move on.
-                // Shebangs are used by Node scripts.
-                if (lines[0] && lines[0].substr(0, 2) === '#!')
-                    lines[0] = '';
-
-                line = 0;
-                nextLine();
-                from = 1;
-            },
-
-            range: function (begin, end) {
-                var c, value = '';
-                from = character;
-                if (s.charAt(0) !== begin) {
-                    errorAt("Expected '{a}' and instead saw '{b}'.",
-                            line, character, begin, s.charAt(0));
-                }
-                for (;;) {
-                    s = s.slice(1);
-                    character += 1;
-                    c = s.charAt(0);
-                    switch (c) {
-                    case '':
-                        errorAt("Missing '{a}'.", line, character, c);
-                        break;
-                    case end:
-                        s = s.slice(1);
-                        character += 1;
-                        return it('(range)', value);
-                    case '\\':
-                        warningAt("Unexpected '{a}'.", line, character, c);
-                    }
-                    value += c;
-                }
-
-            },
-
-
-            // token -- this is called by advance to get the next token
-            token: function () {
-                var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange;
-
-                function match(x) {
-                    var r = x.exec(s), r1;
-                    if (r) {
-                        l = r[0].length;
-                        r1 = r[1];
-                        c = r1.charAt(0);
-                        s = s.substr(l);
-                        from = character + l - r1.length;
-                        character += l;
-                        return r1;
-                    }
-                }
-
-                function string(x) {
-                    var c, j, r = '', allowNewLine = false;
-
-                    if (jsonmode && x !== '"') {
-                        warningAt("Strings must use doublequote.",
-                                line, character);
-                    }
-
-                    function esc(n) {
-                        var i = parseInt(s.substr(j + 1, n), 16);
-                        j += n;
-                        if (i >= 32 && i <= 126 &&
-                                i !== 34 && i !== 92 && i !== 39) {
-                            warningAt("Unnecessary escapement.", line, character);
-                        }
-                        character += n;
-                        c = String.fromCharCode(i);
-                    }
-                    j = 0;
-unclosedString:     for (;;) {
-                        while (j >= s.length) {
-                            j = 0;
-
-                            var cl = line, cf = from;
-                            if (!nextLine()) {
-                                errorAt("Unclosed string.", cl, cf);
-                                break unclosedString;
-                            }
-
-                            if (allowNewLine) {
-                                allowNewLine = false;
-                            } else {
-                                warningAt("Unclosed string.", cl, cf);
-                            }
-                        }
-                        c = s.charAt(j);
-                        if (c === x) {
-                            character += 1;
-                            s = s.substr(j + 1);
-                            return it('(string)', r, x);
-                        }
-                        if (c < ' ') {
-                            if (c === '\n' || c === '\r') {
-                                break;
-                            }
-                            warningAt("Control character in string: {a}.",
-                                    line, character + j, s.slice(0, j));
-                        } else if (c === '\\') {
-                            j += 1;
-                            character += 1;
-                            c = s.charAt(j);
-                            switch (c) {
-                            case '\\':
-                            case '"':
-                            case '/':
-                                break;
-                            case '\'':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\'.", line, character);
-                                }
-                                break;
-                            case 'b':
-                                c = '\b';
-                                break;
-                            case 'f':
-                                c = '\f';
-                                break;
-                            case 'n':
-                                c = '\n';
-                                break;
-                            case 'r':
-                                c = '\r';
-                                break;
-                            case 't':
-                                c = '\t';
-                                break;
-                            case 'u':
-                                esc(4);
-                                break;
-                            case 'v':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\v.", line, character);
-                                }
-                                c = '\v';
-                                break;
-                            case 'x':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\x-.", line, character);
-                                }
-                                esc(2);
-                                break;
-                            case '':
-                                // last character is escape character
-                                // always allow new line if escaped, but show
-                                // warning if option is not set
-                                allowNewLine = true;
-                                if (option.multistr) {
-                                    if (jsonmode) {
-                                        warningAt("Avoid EOL escapement.", line, character);
-                                    }
-                                    c = '';
-                                    character -= 1;
-                                    break;
-                                }
-                                warningAt("Bad escapement of EOL. Use option multistr if needed.",
-                                    line, character);
-                                break;
-                            default:
-                                warningAt("Bad escapement.", line, character);
-                            }
-                        }
-                        r += c;
-                        character += 1;
-                        j += 1;
-                    }
-                }
-
-                for (;;) {
-                    if (!s) {
-                        return it(nextLine() ? '(endline)' : '(end)', '');
-                    }
-                    t = match(tx);
-                    if (!t) {
-                        t = '';
-                        c = '';
-                        while (s && s < '!') {
-                            s = s.substr(1);
-                        }
-                        if (s) {
-                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));
-                            s = '';
-                        }
-                    } else {
-
-    //      identifier
-
-                        if (c.isAlpha() || c === '_' || c === '$') {
-                            return it('(identifier)', t);
-                        }
-
-    //      number
-
-                        if (c.isDigit()) {
-                            if (!isFinite(Number(t))) {
-                                warningAt("Bad number '{a}'.",
-                                    line, character, t);
-                            }
-                            if (s.substr(0, 1).isAlpha()) {
-                                warningAt("Missing space after '{a}'.",
-                                        line, character, t);
-                            }
-                            if (c === '0') {
-                                d = t.substr(1, 1);
-                                if (d.isDigit()) {
-                                    if (token.id !== '.') {
-                                        warningAt("Don't use extra leading zeros '{a}'.",
-                                            line, character, t);
-                                    }
-                                } else if (jsonmode && (d === 'x' || d === 'X')) {
-                                    warningAt("Avoid 0x-. '{a}'.",
-                                            line, character, t);
-                                }
-                            }
-                            if (t.substr(t.length - 1) === '.') {
-                                warningAt(
-"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);
-                            }
-                            return it('(number)', t);
-                        }
-                        switch (t) {
-
-    //      string
-
-                        case '"':
-                        case "'":
-                            return string(t);
-
-    //      // comment
-
-                        case '//':
-                            s = '';
-                            token.comment = true;
-                            break;
-
-    //      /* comment
-
-                        case '/*':
-                            for (;;) {
-                                i = s.search(lx);
-                                if (i >= 0) {
-                                    break;
-                                }
-                                if (!nextLine()) {
-                                    errorAt("Unclosed comment.", line, character);
-                                }
-                            }
-                            character += i + 2;
-                            if (s.substr(i, 1) === '/') {
-                                errorAt("Nested comment.", line, character);
-                            }
-                            s = s.substr(i + 2);
-                            token.comment = true;
-                            break;
-
-    //      /*members /*jshint /*global
-
-                        case '/*members':
-                        case '/*member':
-                        case '/*jshint':
-                        case '/*jslint':
-                        case '/*global':
-                        case '*/':
-                            return {
-                                value: t,
-                                type: 'special',
-                                line: line,
-                                character: character,
-                                from: from
-                            };
-
-                        case '':
-                            break;
-    //      /
-                        case '/':
-                            if (token.id === '/=') {
-                                errorAt("A regular expression literal can be confused with '/='.",
-                                    line, from);
-                            }
-                            if (prereg) {
-                                depth = 0;
-                                captures = 0;
-                                l = 0;
-                                for (;;) {
-                                    b = true;
-                                    c = s.charAt(l);
-                                    l += 1;
-                                    switch (c) {
-                                    case '':
-                                        errorAt("Unclosed regular expression.", line, from);
-                                        return quit('Stopping.', line, from);
-                                    case '/':
-                                        if (depth > 0) {
-                                            warningAt("{a} unterminated regular expression " +
-                                                "group(s).", line, from + l, depth);
-                                        }
-                                        c = s.substr(0, l - 1);
-                                        q = {
-                                            g: true,
-                                            i: true,
-                                            m: true
-                                        };
-                                        while (q[s.charAt(l)] === true) {
-                                            q[s.charAt(l)] = false;
-                                            l += 1;
-                                        }
-                                        character += l;
-                                        s = s.substr(l);
-                                        q = s.charAt(0);
-                                        if (q === '/' || q === '*') {
-                                            errorAt("Confusing regular expression.",
-                                                    line, from);
-                                        }
-                                        return it('(regexp)', c);
-                                    case '\\':
-                                        c = s.charAt(l);
-                                        if (c < ' ') {
-                                            warningAt(
-"Unexpected control character in regular expression.", line, from + l);
-                                        } else if (c === '<') {
-                                            warningAt(
-"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
-                                        }
-                                        l += 1;
-                                        break;
-                                    case '(':
-                                        depth += 1;
-                                        b = false;
-                                        if (s.charAt(l) === '?') {
-                                            l += 1;
-                                            switch (s.charAt(l)) {
-                                            case ':':
-                                            case '=':
-                                            case '!':
-                                                l += 1;
-                                                break;
-                                            default:
-                                                warningAt(
-"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
-                                            }
-                                        } else {
-                                            captures += 1;
-                                        }
-                                        break;
-                                    case '|':
-                                        b = false;
-                                        break;
-                                    case ')':
-                                        if (depth === 0) {
-                                            warningAt("Unescaped '{a}'.",
-                                                    line, from + l, ')');
-                                        } else {
-                                            depth -= 1;
-                                        }
-                                        break;
-                                    case ' ':
-                                        q = 1;
-                                        while (s.charAt(l) === ' ') {
-                                            l += 1;
-                                            q += 1;
-                                        }
-                                        if (q > 1) {
-                                            warningAt(
-"Spaces are hard to count. Use {{a}}.", line, from + l, q);
-                                        }
-                                        break;
-                                    case '[':
-                                        c = s.charAt(l);
-                                        if (c === '^') {
-                                            l += 1;
-                                            if (option.regexp) {
-                                                warningAt("Insecure '{a}'.",
-                                                        line, from + l, c);
-                                            } else if (s.charAt(l) === ']') {
-                                                errorAt("Unescaped '{a}'.",
-                                                    line, from + l, '^');
-                                            }
-                                        }
-                                        if (c === ']') {
-                                            warningAt("Empty class.", line,
-                                                    from + l - 1);
-                                        }
-                                        isLiteral = false;
-                                        isInRange = false;
-klass:                                  do {
-                                            c = s.charAt(l);
-                                            l += 1;
-                                            switch (c) {
-                                            case '[':
-                                            case '^':
-                                                warningAt("Unescaped '{a}'.",
-                                                        line, from + l, c);
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '-':
-                                                if (isLiteral && !isInRange) {
-                                                    isLiteral = false;
-                                                    isInRange = true;
-                                                } else if (isInRange) {
-                                                    isInRange = false;
-                                                } else if (s.charAt(l) === ']') {
-                                                    isInRange = true;
-                                                } else {
-                                                    if (option.regexdash !== (l === 2 || (l === 3 &&
-                                                        s.charAt(1) === '^'))) {
-                                                        warningAt("Unescaped '{a}'.",
-                                                            line, from + l - 1, '-');
-                                                    }
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case ']':
-                                                if (isInRange && !option.regexdash) {
-                                                    warningAt("Unescaped '{a}'.",
-                                                            line, from + l - 1, '-');
-                                                }
-                                                break klass;
-                                            case '\\':
-                                                c = s.charAt(l);
-                                                if (c < ' ') {
-                                                    warningAt(
-"Unexpected control character in regular expression.", line, from + l);
-                                                } else if (c === '<') {
-                                                    warningAt(
-"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
-                                                }
-                                                l += 1;
-
-                                                // \w, \s and \d are never part of a character range
-                                                if (/[wsd]/i.test(c)) {
-                                                    if (isInRange) {
-                                                        warningAt("Unescaped '{a}'.",
-                                                            line, from + l, '-');
-                                                        isInRange = false;
-                                                    }
-                                                    isLiteral = false;
-                                                } else if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '/':
-                                                warningAt("Unescaped '{a}'.",
-                                                        line, from + l - 1, '/');
-
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '<':
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            default:
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                            }
-                                        } while (c);
-                                        break;
-                                    case '.':
-                                        if (option.regexp) {
-                                            warningAt("Insecure '{a}'.", line,
-                                                    from + l, c);
-                                        }
-                                        break;
-                                    case ']':
-                                    case '?':
-                                    case '{':
-                                    case '}':
-                                    case '+':
-                                    case '*':
-                                        warningAt("Unescaped '{a}'.", line,
-                                                from + l, c);
-                                    }
-                                    if (b) {
-                                        switch (s.charAt(l)) {
-                                        case '?':
-                                        case '+':
-                                        case '*':
-                                            l += 1;
-                                            if (s.charAt(l) === '?') {
-                                                l += 1;
-                                            }
-                                            break;
-                                        case '{':
-                                            l += 1;
-                                            c = s.charAt(l);
-                                            if (c < '0' || c > '9') {
-                                                warningAt(
-"Expected a number and instead saw '{a}'.", line, from + l, c);
-                                            }
-                                            l += 1;
-                                            low = +c;
-                                            for (;;) {
-                                                c = s.charAt(l);
-                                                if (c < '0' || c > '9') {
-                                                    break;
-                                                }
-                                                l += 1;
-                                                low = +c + (low * 10);
-                                            }
-                                            high = low;
-                                            if (c === ',') {
-                                                l += 1;
-                                                high = Infinity;
-                                                c = s.charAt(l);
-                                                if (c >= '0' && c <= '9') {
-                                                    l += 1;
-                                                    high = +c;
-                                                    for (;;) {
-                                                        c = s.charAt(l);
-                                                        if (c < '0' || c > '9') {
-                                                            break;
-                                                        }
-                                                        l += 1;
-                                                        high = +c + (high * 10);
-                                                    }
-                                                }
-                                            }
-                                            if (s.charAt(l) !== '}') {
-                                                warningAt(
-"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
-                                            } else {
-                                                l += 1;
-                                            }
-                                            if (s.charAt(l) === '?') {
-                                                l += 1;
-                                            }
-                                            if (low > high) {
-                                                warningAt(
-"'{a}' should not be greater than '{b}'.", line, from + l, low, high);
-                                            }
-                                        }
-                                    }
-                                }
-                                c = s.substr(0, l - 1);
-                                character += l;
-                                s = s.substr(l);
-                                return it('(regexp)', c);
-                            }
-                            return it('(punctuator)', t);
-
-    //      punctuator
-
-                        case '#':
-                            return it('(punctuator)', t);
-                        default:
-                            return it('(punctuator)', t);
-                        }
-                    }
-                }
-            }
-        };
-    }());
-
-
-    function addlabel(t, type) {
-
-        if (t === 'hasOwnProperty') {
-            warning("'hasOwnProperty' is a really bad name.");
-        }
-
-// Define t in the current function in the current scope.
-        if (is_own(funct, t) && !funct['(global)']) {
-            if (funct[t] === true) {
-                if (option.latedef)
-                    warning("'{a}' was used before it was defined.", nexttoken, t);
-            } else {
-                if (!option.shadow && type !== "exception")
-                    warning("'{a}' is already defined.", nexttoken, t);
-            }
-        }
-
-        funct[t] = type;
-        if (funct['(global)']) {
-            global[t] = funct;
-            if (is_own(implied, t)) {
-                if (option.latedef)
-                    warning("'{a}' was used before it was defined.", nexttoken, t);
-                delete implied[t];
-            }
-        } else {
-            scope[t] = funct;
-        }
-    }
-
-
-    function doOption() {
-        var b, obj, filter, o = nexttoken.value, t, v;
-        switch (o) {
-        case '*/':
-            error("Unbegun comment.");
-            break;
-        case '/*members':
-        case '/*member':
-            o = '/*members';
-            if (!membersOnly) {
-                membersOnly = {};
-            }
-            obj = membersOnly;
-            break;
-        case '/*jshint':
-        case '/*jslint':
-            obj = option;
-            filter = boolOptions;
-            break;
-        case '/*global':
-            obj = predefined;
-            break;
-        default:
-            error("What?");
-        }
-        t = lex.token();
-loop:   for (;;) {
-            for (;;) {
-                if (t.type === 'special' && t.value === '*/') {
-                    break loop;
-                }
-                if (t.id !== '(endline)' && t.id !== ',') {
-                    break;
-                }
-                t = lex.token();
-            }
-            if (t.type !== '(string)' && t.type !== '(identifier)' &&
-                    o !== '/*members') {
-                error("Bad option.", t);
-            }
-            v = lex.token();
-            if (v.id === ':') {
-                v = lex.token();
-                if (obj === membersOnly) {
-                    error("Expected '{a}' and instead saw '{b}'.",
-                            t, '*/', ':');
-                }
-                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.white = true;
-                    obj.indent = b;
-                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.maxerr = b;
-                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.maxlen = b;
-                } else if (t.value === 'validthis') {
-                    if (funct['(global)']) {
-                        error("Option 'validthis' can't be used in a global scope.");
-                    } else {
-                        if (v.value === 'true' || v.value === 'false')
-                            obj[t.value] = v.value === 'true';
-                        else
-                            error("Bad option value.", v);
-                    }
-                } else if (v.value === 'true') {
-                    obj[t.value] = true;
-                } else if (v.value === 'false') {
-                    obj[t.value] = false;
-                } else {
-                    error("Bad option value.", v);
-                }
-                t = lex.token();
-            } else {
-                if (o === '/*jshint' || o === '/*jslint') {
-                    error("Missing option value.", t);
-                }
-                obj[t.value] = false;
-                t = v;
-            }
-        }
-        if (filter) {
-            assume();
-        }
-    }
-
-
-// We need a peek function. If it has an argument, it peeks that much farther
-// ahead. It is used to distinguish
-//     for ( var i in ...
-// from
-//     for ( var i = ...
-
-    function peek(p) {
-        var i = p || 0, j = 0, t;
-
-        while (j <= i) {
-            t = lookahead[j];
-            if (!t) {
-                t = lookahead[j] = lex.token();
-            }
-            j += 1;
-        }
-        return t;
-    }
-
-
-
-// Produce the next token. It looks for programming errors.
-
-    function advance(id, t) {
-        switch (token.id) {
-        case '(number)':
-            if (nexttoken.id === '.') {
-                warning("A dot following a number can be confused with a decimal point.", token);
-            }
-            break;
-        case '-':
-            if (nexttoken.id === '-' || nexttoken.id === '--') {
-                warning("Confusing minusses.");
-            }
-            break;
-        case '+':
-            if (nexttoken.id === '+' || nexttoken.id === '++') {
-                warning("Confusing plusses.");
-            }
-            break;
-        }
-
-        if (token.type === '(string)' || token.identifier) {
-            anonname = token.value;
-        }
-
-        if (id && nexttoken.id !== id) {
-            if (t) {
-                if (nexttoken.id === '(end)') {
-                    warning("Unmatched '{a}'.", t, t.id);
-                } else {
-                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
-                            nexttoken, id, t.id, t.line, nexttoken.value);
-                }
-            } else if (nexttoken.type !== '(identifier)' ||
-                            nexttoken.value !== id) {
-                warning("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, id, nexttoken.value);
-            }
-        }
-
-        prevtoken = token;
-        token = nexttoken;
-        for (;;) {
-            nexttoken = lookahead.shift() || lex.token();
-            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
-                return;
-            }
-            if (nexttoken.type === 'special') {
-                doOption();
-            } else {
-                if (nexttoken.id !== '(endline)') {
-                    break;
-                }
-            }
-        }
-    }
-
-
-// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
-// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
-// like .nud except that it is only used on the first token of a statement.
-// Having .fud makes it much easier to define statement-oriented languages like
-// JavaScript. I retained Pratt's nomenclature.
-
-// .nud     Null denotation
-// .fud     First null denotation
-// .led     Left denotation
-//  lbp     Left binding power
-//  rbp     Right binding power
-
-// They are elements of the parsing method called Top Down Operator Precedence.
-
-    function expression(rbp, initial) {
-        var left, isArray = false;
-
-        if (nexttoken.id === '(end)')
-            error("Unexpected early end of program.", token);
-
-        advance();
-        if (initial) {
-            anonname = 'anonymous';
-            funct['(verb)'] = token.value;
-        }
-        if (initial === true && token.fud) {
-            left = token.fud();
-        } else {
-            if (token.nud) {
-                left = token.nud();
-            } else {
-                if (nexttoken.type === '(number)' && token.id === '.') {
-                    warning("A leading decimal point can be confused with a dot: '.{a}'.",
-                            token, nexttoken.value);
-                    advance();
-                    return token;
-                } else {
-                    error("Expected an identifier and instead saw '{a}'.",
-                            token, token.id);
-                }
-            }
-            while (rbp < nexttoken.lbp) {
-                isArray = token.value === 'Array';
-                advance();
-                if (isArray && token.id === '(' && nexttoken.id === ')')
-                    warning("Use the array literal notation [].", token);
-                if (token.led) {
-                    left = token.led(left);
-                } else {
-                    error("Expected an operator and instead saw '{a}'.",
-                        token, token.id);
-                }
-            }
-        }
-        return left;
-    }
-
-
-// Functions for conformance of style.
-
-    function adjacent(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white) {
-            if (left.character !== right.from && left.line === right.line) {
-                left.from += (left.character - left.from);
-                warning("Unexpected space after '{a}'.", left, left.value);
-            }
-        }
-    }
-
-    function nobreak(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white && (left.character !== right.from || left.line !== right.line)) {
-            warning("Unexpected space before '{a}'.", right, right.value);
-        }
-    }
-
-    function nospace(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white && !left.comment) {
-            if (left.line === right.line) {
-                adjacent(left, right);
-            }
-        }
-    }
-
-    function nonadjacent(left, right) {
-        if (option.white) {
-            left = left || token;
-            right = right || nexttoken;
-            if (left.line === right.line && left.character === right.from) {
-                left.from += (left.character - left.from);
-                warning("Missing space after '{a}'.",
-                        left, left.value);
-            }
-        }
-    }
-
-    function nobreaknonadjacent(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (!option.laxbreak && left.line !== right.line) {
-            warning("Bad line breaking before '{a}'.", right, right.id);
-        } else if (option.white) {
-            left = left || token;
-            right = right || nexttoken;
-            if (left.character === right.from) {
-                left.from += (left.character - left.from);
-                warning("Missing space after '{a}'.",
-                        left, left.value);
-            }
-        }
-    }
-
-    function indentation(bias) {
-        var i;
-        if (option.white && nexttoken.id !== '(end)') {
-            i = indent + (bias || 0);
-            if (nexttoken.from !== i) {
-                warning(
-"Expected '{a}' to have an indentation at {b} instead at {c}.",
-                        nexttoken, nexttoken.value, i, nexttoken.from);
-            }
-        }
-    }
-
-    function nolinebreak(t) {
-        t = t || token;
-        if (t.line !== nexttoken.line) {
-            warning("Line breaking error '{a}'.", t, t.value);
-        }
-    }
-
-
-    function comma() {
-        if (token.line !== nexttoken.line) {
-            if (!option.laxbreak) {
-                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
-            }
-        } else if (!token.comment && token.character !== nexttoken.from && option.white) {
-            token.from += (token.character - token.from);
-            warning("Unexpected space after '{a}'.", token, token.value);
-        }
-        advance(',');
-        nonadjacent(token, nexttoken);
-    }
-
-
-// Functional constructors for making the symbols that will be inherited by
-// tokens.
-
-    function symbol(s, p) {
-        var x = syntax[s];
-        if (!x || typeof x !== 'object') {
-            syntax[s] = x = {
-                id: s,
-                lbp: p,
-                value: s
-            };
-        }
-        return x;
-    }
-
-
-    function delim(s) {
-        return symbol(s, 0);
-    }
-
-
-    function stmt(s, f) {
-        var x = delim(s);
-        x.identifier = x.reserved = true;
-        x.fud = f;
-        return x;
-    }
-
-
-    function blockstmt(s, f) {
-        var x = stmt(s, f);
-        x.block = true;
-        return x;
-    }
-
-
-    function reserveName(x) {
-        var c = x.id.charAt(0);
-        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
-            x.identifier = x.reserved = true;
-        }
-        return x;
-    }
-
-
-    function prefix(s, f) {
-        var x = symbol(s, 150);
-        reserveName(x);
-        x.nud = (typeof f === 'function') ? f : function () {
-            this.right = expression(150);
-            this.arity = 'unary';
-            if (this.id === '++' || this.id === '--') {
-                if (option.plusplus) {
-                    warning("Unexpected use of '{a}'.", this, this.id);
-                } else if ((!this.right.identifier || this.right.reserved) &&
-                        this.right.id !== '.' && this.right.id !== '[') {
-                    warning("Bad operand.", this);
-                }
-            }
-            return this;
-        };
-        return x;
-    }
-
-
-    function type(s, f) {
-        var x = delim(s);
-        x.type = s;
-        x.nud = f;
-        return x;
-    }
-
-
-    function reserve(s, f) {
-        var x = type(s, f);
-        x.identifier = x.reserved = true;
-        return x;
-    }
-
-
-    function reservevar(s, v) {
-        return reserve(s, function () {
-            if (typeof v === 'function') {
-                v(this);
-            }
-            return this;
-        });
-    }
-
-
-    function infix(s, f, p, w) {
-        var x = symbol(s, p);
-        reserveName(x);
-        x.led = function (left) {
-            if (!w) {
-                nobreaknonadjacent(prevtoken, token);
-                nonadjacent(token, nexttoken);
-            }
-            if (s === "in" && left.id === "!") {
-                warning("Confusing use of '{a}'.", left, '!');
-            }
-            if (typeof f === 'function') {
-                return f(left, this);
-            } else {
-                this.left = left;
-                this.right = expression(p);
-                return this;
-            }
-        };
-        return x;
-    }
-
-
-    function relation(s, f) {
-        var x = symbol(s, 100);
-        x.led = function (left) {
-            nobreaknonadjacent(prevtoken, token);
-            nonadjacent(token, nexttoken);
-            var right = expression(100);
-            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
-                warning("Use the isNaN function to compare with NaN.", this);
-            } else if (f) {
-                f.apply(this, [left, right]);
-            }
-            if (left.id === '!') {
-                warning("Confusing use of '{a}'.", left, '!');
-            }
-            if (right.id === '!') {
-                warning("Confusing use of '{a}'.", right, '!');
-            }
-            this.left = left;
-            this.right = right;
-            return this;
-        };
-        return x;
-    }
-
-
-    function isPoorRelation(node) {
-        return node &&
-              ((node.type === '(number)' && +node.value === 0) ||
-               (node.type === '(string)' && node.value === '') ||
-               (node.type === 'null' && !option.eqnull) ||
-                node.type === 'true' ||
-                node.type === 'false' ||
-                node.type === 'undefined');
-    }
-
-
-    function assignop(s, f) {
-        symbol(s, 20).exps = true;
-        return infix(s, function (left, that) {
-            var l;
-            that.left = left;
-            if (predefined[left.value] === false &&
-                    scope[left.value]['(global)'] === true) {
-                warning("Read only.", left);
-            } else if (left['function']) {
-                warning("'{a}' is a function.", left, left.value);
-            }
-            if (left) {
-                if (option.esnext && funct[left.value] === 'const') {
-                    warning("Attempting to override '{a}' which is a constant", left, left.value);
-                }
-                if (left.id === '.' || left.id === '[') {
-                    if (!left.left || left.left.value === 'arguments') {
-                        warning('Bad assignment.', that);
-                    }
-                    that.right = expression(19);
-                    return that;
-                } else if (left.identifier && !left.reserved) {
-                    if (funct[left.value] === 'exception') {
-                        warning("Do not assign to the exception parameter.", left);
-                    }
-                    that.right = expression(19);
-                    return that;
-                }
-                if (left === syntax['function']) {
-                    warning(
-"Expected an identifier in an assignment and instead saw a function invocation.",
-                                token);
-                }
-            }
-            error("Bad assignment.", that);
-        }, 20);
-    }
-
-
-    function bitwise(s, f, p) {
-        var x = symbol(s, p);
-        reserveName(x);
-        x.led = (typeof f === 'function') ? f : function (left) {
-            if (option.bitwise) {
-                warning("Unexpected use of '{a}'.", this, this.id);
-            }
-            this.left = left;
-            this.right = expression(p);
-            return this;
-        };
-        return x;
-    }
-
-
-    function bitwiseassignop(s) {
-        symbol(s, 20).exps = true;
-        return infix(s, function (left, that) {
-            if (option.bitwise) {
-                warning("Unexpected use of '{a}'.", that, that.id);
-            }
-            nonadjacent(prevtoken, token);
-            nonadjacent(token, nexttoken);
-            if (left) {
-                if (left.id === '.' || left.id === '[' ||
-                        (left.identifier && !left.reserved)) {
-                    expression(19);
-                    return that;
-                }
-                if (left === syntax['function']) {
-                    warning(
-"Expected an identifier in an assignment, and instead saw a function invocation.",
-                                token);
-                }
-                return that;
-            }
-            error("Bad assignment.", that);
-        }, 20);
-    }
-
-
-    function suffix(s, f) {
-        var x = symbol(s, 150);
-        x.led = function (left) {
-            if (option.plusplus) {
-                warning("Unexpected use of '{a}'.", this, this.id);
-            } else if ((!left.identifier || left.reserved) &&
-                    left.id !== '.' && left.id !== '[') {
-                warning("Bad operand.", this);
-            }
-            this.left = left;
-            return this;
-        };
-        return x;
-    }
-
-
-    // fnparam means that this identifier is being defined as a function
-    // argument (see identifier())
-    function optionalidentifier(fnparam) {
-        if (nexttoken.identifier) {
-            advance();
-            if (token.reserved && !option.es5) {
-                // `undefined` as a function param is a common pattern to protect
-                // against the case when somebody does `undefined = true` and
-                // help with minification. More info: https://gist.github.com/315916
-                if (!fnparam || token.value !== 'undefined') {
-                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",
-                            token, token.id);
-                }
-            }
-            return token.value;
-        }
-    }
-
-    // fnparam means that this identifier is being defined as a function
-    // argument
-    function identifier(fnparam) {
-        var i = optionalidentifier(fnparam);
-        if (i) {
-            return i;
-        }
-        if (token.id === 'function' && nexttoken.id === '(') {
-            warning("Missing name in function declaration.");
-        } else {
-            error("Expected an identifier and instead saw '{a}'.",
-                    nexttoken, nexttoken.value);
-        }
-    }
-
-
-    function reachable(s) {
-        var i = 0, t;
-        if (nexttoken.id !== ';' || noreach) {
-            return;
-        }
-        for (;;) {
-            t = peek(i);
-            if (t.reach) {
-                return;
-            }
-            if (t.id !== '(endline)') {
-                if (t.id === 'function') {
-                    warning(
-"Inner functions should be listed at the top of the outer function.", t);
-                    break;
-                }
-                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
-                break;
-            }
-            i += 1;
-        }
-    }
-
-
-    function statement(noindent) {
-        var i = indent, r, s = scope, t = nexttoken;
-
-        if (t.id === ";") {
-            advance(";");
-            return;
-        }
-
-// Is this a labelled statement?
-
-        if (t.identifier && !t.reserved && peek().id === ':') {
-            advance();
-            advance(':');
-            scope = Object.create(s);
-            addlabel(t.value, 'label');
-            if (!nexttoken.labelled) {
-                warning("Label '{a}' on {b} statement.",
-                        nexttoken, t.value, nexttoken.value);
-            }
-            if (jx.test(t.value + ':')) {
-                warning("Label '{a}' looks like a javascript url.",
-                        t, t.value);
-            }
-            nexttoken.label = t.value;
-            t = nexttoken;
-        }
-
-// Parse the statement.
-
-        if (!noindent) {
-            indentation();
-        }
-        r = expression(0, true);
-
-        // Look for the final semicolon.
-        if (!t.block) {
-            if (!option.expr && (!r || !r.exps)) {
-                warning("Expected an assignment or function call and instead saw an expression.",
-                    token);
-            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {
-                warning("Do not use 'new' for side effects.");
-            }
-
-            if (nexttoken.id !== ';') {
-                if (!option.asi) {
-                    // If this is the last statement in a block that ends on
-                    // the same line *and* option lastsemic is on, ignore the warning.
-                    // Otherwise, complain about missing semicolon.
-                    if (!option.lastsemic || nexttoken.id !== '}' ||
-                            nexttoken.line !== token.line) {
-                        warningAt("Missing semicolon.", token.line, token.character);
-                    }
-                }
-            } else {
-                adjacent(token, nexttoken);
-                advance(';');
-                nonadjacent(token, nexttoken);
-            }
-        }
-
-// Restore the indentation.
-
-        indent = i;
-        scope = s;
-        return r;
-    }
-
-
-    function statements(startLine) {
-        var a = [], f, p;
-
-        while (!nexttoken.reach && nexttoken.id !== '(end)') {
-            if (nexttoken.id === ';') {
-                warning("Unnecessary semicolon.");
-                advance(';');
-            } else {
-                a.push(statement(startLine === nexttoken.line));
-            }
-        }
-        return a;
-    }
-
-
-    /*
-     * read all directives
-     * recognizes a simple form of asi, but always
-     * warns, if it is used
-     */
-    function directives() {
-        var i, p, pn;
-
-        for (;;) {
-            if (nexttoken.id === "(string)") {
-                p = peek(0);
-                if (p.id === "(endline)") {
-                    i = 1;
-                    do {
-                        pn = peek(i);
-                        i = i + 1;
-                    } while (pn.id === "(endline)");
-
-                    if (pn.id !== ";") {
-                        if (pn.id !== "(string)" && pn.id !== "(number)" &&
-                            pn.id !== "(regexp)" && pn.identifier !== true &&
-                            pn.id !== "}") {
-                            break;
-                        }
-                        warning("Missing semicolon.", nexttoken);
-                    } else {
-                        p = pn;
-                    }
-                } else if (p.id === "}") {
-                    // directive with no other statements, warn about missing semicolon
-                    warning("Missing semicolon.", p);
-                } else if (p.id !== ";") {
-                    break;
-                }
-
-                indentation();
-                advance();
-                if (directive[token.value]) {
-                    warning("Unnecessary directive \"{a}\".", token, token.value);
-                }
-
-                if (token.value === "use strict") {
-                    option.newcap = true;
-                    option.undef = true;
-                }
-
-                // there's no directive negation, so always set to true
-                directive[token.value] = true;
-
-                if (p.id === ";") {
-                    advance(";");
-                }
-                continue;
-            }
-            break;
-        }
-    }
-
-
-    /*
-     * Parses a single block. A block is a sequence of statements wrapped in
-     * braces.
-     *
-     * ordinary - true for everything but function bodies and try blocks.
-     * stmt     - true if block can be a single statement (e.g. in if/for/while).
-     * isfunc   - true if block is a function body
-     */
-    function block(ordinary, stmt, isfunc) {
-        var a,
-            b = inblock,
-            old_indent = indent,
-            m,
-            s = scope,
-            t,
-            line,
-            d;
-
-        inblock = ordinary;
-        if (!ordinary || !option.funcscope) scope = Object.create(scope);
-        nonadjacent(token, nexttoken);
-        t = nexttoken;
-
-        if (nexttoken.id === '{') {
-            advance('{');
-            line = token.line;
-            if (nexttoken.id !== '}') {
-                indent += option.indent;
-                while (!ordinary && nexttoken.from > indent) {
-                    indent += option.indent;
-                }
-
-                if (isfunc) {
-                    m = {};
-                    for (d in directive) {
-                        if (is_own(directive, d)) {
-                            m[d] = directive[d];
-                        }
-                    }
-                    directives();
-
-                    if (option.strict && funct['(context)']['(global)']) {
-                        if (!m["use strict"] && !directive["use strict"]) {
-                            warning("Missing \"use strict\" statement.");
-                        }
-                    }
-                }
-
-                a = statements(line);
-
-                if (isfunc) {
-                    directive = m;
-                }
-
-                indent -= option.indent;
-                if (line !== nexttoken.line) {
-                    indentation();
-                }
-            } else if (line !== nexttoken.line) {
-                indentation();
-            }
-            advance('}', t);
-            indent = old_indent;
-        } else if (!ordinary) {
-            error("Expected '{a}' and instead saw '{b}'.",
-                  nexttoken, '{', nexttoken.value);
-        } else {
-            if (!stmt || option.curly)
-                warning("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, '{', nexttoken.value);
-
-            noreach = true;
-            indent += option.indent;
-            // test indentation only if statement is in new line
-            a = [statement(nexttoken.line === token.line)];
-            indent -= option.indent;
-            noreach = false;
-        }
-        funct['(verb)'] = null;
-        if (!ordinary || !option.funcscope) scope = s;
-        inblock = b;
-        if (ordinary && option.noempty && (!a || a.length === 0)) {
-            warning("Empty block.");
-        }
-        return a;
-    }
-
-
-    function countMember(m) {
-        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
-            warning("Unexpected /*member '{a}'.", token, m);
-        }
-        if (typeof member[m] === 'number') {
-            member[m] += 1;
-        } else {
-            member[m] = 1;
-        }
-    }
-
-
-    function note_implied(token) {
-        var name = token.value, line = token.line, a = implied[name];
-        if (typeof a === 'function') {
-            a = false;
-        }
-        if (!a) {
-            a = [line];
-            implied[name] = a;
-        } else if (a[a.length - 1] !== line) {
-            a.push(line);
-        }
-    }
-
-
-    // Build the syntax table by declaring the syntactic elements of the language.
-
-    type('(number)', function () {
-        return this;
-    });
-
-    type('(string)', function () {
-        return this;
-    });
-
-    syntax['(identifier)'] = {
-        type: '(identifier)',
-        lbp: 0,
-        identifier: true,
-        nud: function () {
-            var v = this.value,
-                s = scope[v],
-                f;
-
-            if (typeof s === 'function') {
-                // Protection against accidental inheritance.
-                s = undefined;
-            } else if (typeof s === 'boolean') {
-                f = funct;
-                funct = functions[0];
-                addlabel(v, 'var');
-                s = funct;
-                funct = f;
-            }
-
-            // The name is in scope and defined in the current function.
-            if (funct === s) {
-                // Change 'unused' to 'var', and reject labels.
-                switch (funct[v]) {
-                case 'unused':
-                    funct[v] = 'var';
-                    break;
-                case 'unction':
-                    funct[v] = 'function';
-                    this['function'] = true;
-                    break;
-                case 'function':
-                    this['function'] = true;
-                    break;
-                case 'label':
-                    warning("'{a}' is a statement label.", token, v);
-                    break;
-                }
-            } else if (funct['(global)']) {
-                // The name is not defined in the function.  If we are in the global
-                // scope, then we have an undefined variable.
-                //
-                // Operators typeof and delete do not raise runtime errors even if
-                // the base object of a reference is null so no need to display warning
-                // if we're inside of typeof or delete.
-                if (anonname !== 'typeof' && anonname !== 'delete' &&
-                    option.undef && typeof predefined[v] !== 'boolean') {
-                    isundef(funct, "'{a}' is not defined.", token, v);
-                }
-                note_implied(token);
-            } else {
-                // If the name is already defined in the current
-                // function, but not as outer, then there is a scope error.
-
-                switch (funct[v]) {
-                case 'closure':
-                case 'function':
-                case 'var':
-                case 'unused':
-                    warning("'{a}' used out of scope.", token, v);
-                    break;
-                case 'label':
-                    warning("'{a}' is a statement label.", token, v);
-                    break;
-                case 'outer':
-                case 'global':
-                    break;
-                default:
-                    // If the name is defined in an outer function, make an outer entry,
-                    // and if it was unused, make it var.
-                    if (s === true) {
-                        funct[v] = true;
-                    } else if (s === null) {
-                        warning("'{a}' is not allowed.", token, v);
-                        note_implied(token);
-                    } else if (typeof s !== 'object') {
-                        // Operators typeof and delete do not raise runtime errors even
-                        // if the base object of a reference is null so no need to
-                        // display warning if we're inside of typeof or delete.
-                        if (anonname !== 'typeof' && anonname !== 'delete' && option.undef) {
-                            isundef(funct, "'{a}' is not defined.", token, v);
-                        }
-                        funct[v] = true;
-                        note_implied(token);
-                    } else {
-                        switch (s[v]) {
-                        case 'function':
-                        case 'unction':
-                            this['function'] = true;
-                            s[v] = 'closure';
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'var':
-                        case 'unused':
-                            s[v] = 'closure';
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'closure':
-                        case 'parameter':
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'label':
-                            warning("'{a}' is a statement label.", token, v);
-                        }
-                    }
-                }
-            }
-            return this;
-        },
-        led: function () {
-            error("Expected an operator and instead saw '{a}'.",
-                nexttoken, nexttoken.value);
-        }
-    };
-
-    type('(regexp)', function () {
-        return this;
-    });
-
-
-// ECMAScript parser
-
-    delim('(endline)');
-    delim('(begin)');
-    delim('(end)').reach = true;
-    delim('</').reach = true;
-    delim('<!');
-    delim('<!--');
-    delim('-->');
-    delim('(error)').reach = true;
-    delim('}').reach = true;
-    delim(')');
-    delim(']');
-    delim('"').reach = true;
-    delim("'").reach = true;
-    delim(';');
-    delim(':').reach = true;
-    delim(',');
-    delim('#');
-    delim('@');
-    reserve('else');
-    reserve('case').reach = true;
-    reserve('catch');
-    reserve('default').reach = true;
-    reserve('finally');
-    reservevar('arguments', function (x) {
-        if (directive['use strict'] && funct['(global)']) {
-            warning("Strict violation.", x);
-        }
-    });
-    reservevar('eval');
-    reservevar('false');
-    reservevar('Infinity');
-    reservevar('NaN');
-    reservevar('null');
-    reservevar('this', function (x) {
-        if (directive['use strict'] && !option.validthis && ((funct['(statement)'] &&
-                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {
-            warning("Possible strict violation.", x);
-        }
-    });
-    reservevar('true');
-    reservevar('undefined');
-    assignop('=', 'assign', 20);
-    assignop('+=', 'assignadd', 20);
-    assignop('-=', 'assignsub', 20);
-    assignop('*=', 'assignmult', 20);
-    assignop('/=', 'assigndiv', 20).nud = function () {
-        error("A regular expression literal can be confused with '/='.");
-    };
-    assignop('%=', 'assignmod', 20);
-    bitwiseassignop('&=', 'assignbitand', 20);
-    bitwiseassignop('|=', 'assignbitor', 20);
-    bitwiseassignop('^=', 'assignbitxor', 20);
-    bitwiseassignop('<<=', 'assignshiftleft', 20);
-    bitwiseassignop('>>=', 'assignshiftright', 20);
-    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
-    infix('?', function (left, that) {
-        that.left = left;
-        that.right = expression(10);
-        advance(':');
-        that['else'] = expression(10);
-        return that;
-    }, 30);
-
-    infix('||', 'or', 40);
-    infix('&&', 'and', 50);
-    bitwise('|', 'bitor', 70);
-    bitwise('^', 'bitxor', 80);
-    bitwise('&', 'bitand', 90);
-    relation('==', function (left, right) {
-        var eqnull = option.eqnull && (left.value === 'null' || right.value === 'null');
-
-        if (!eqnull && option.eqeqeq)
-            warning("Expected '{a}' and instead saw '{b}'.", this, '===', '==');
-        else if (isPoorRelation(left))
-            warning("Use '{a}' to compare with '{b}'.", this, '===', left.value);
-        else if (isPoorRelation(right))
-            warning("Use '{a}' to compare with '{b}'.", this, '===', right.value);
-
-        return this;
-    });
-    relation('===');
-    relation('!=', function (left, right) {
-        var eqnull = option.eqnull &&
-                (left.value === 'null' || right.value === 'null');
-
-        if (!eqnull && option.eqeqeq) {
-            warning("Expected '{a}' and instead saw '{b}'.",
-                    this, '!==', '!=');
-        } else if (isPoorRelation(left)) {
-            warning("Use '{a}' to compare with '{b}'.",
-                    this, '!==', left.value);
-        } else if (isPoorRelation(right)) {
-            warning("Use '{a}' to compare with '{b}'.",
-                    this, '!==', right.value);
-        }
-        return this;
-    });
-    relation('!==');
-    relation('<');
-    relation('>');
-    relation('<=');
-    relation('>=');
-    bitwise('<<', 'shiftleft', 120);
-    bitwise('>>', 'shiftright', 120);
-    bitwise('>>>', 'shiftrightunsigned', 120);
-    infix('in', 'in', 120);
-    infix('instanceof', 'instanceof', 120);
-    infix('+', function (left, that) {
-        var right = expression(130);
-        if (left && right && left.id === '(string)' && right.id === '(string)') {
-            left.value += right.value;
-            left.character = right.character;
-            if (!option.scripturl && jx.test(left.value)) {
-                warning("JavaScript URL.", left);
-            }
-            return left;
-        }
-        that.left = left;
-        that.right = right;
-        return that;
-    }, 130);
-    prefix('+', 'num');
-    prefix('+++', function () {
-        warning("Confusing pluses.");
-        this.right = expression(150);
-        this.arity = 'unary';
-        return this;
-    });
-    infix('+++', function (left) {
-        warning("Confusing pluses.");
-        this.left = left;
-        this.right = expression(130);
-        return this;
-    }, 130);
-    infix('-', 'sub', 130);
-    prefix('-', 'neg');
-    prefix('---', function () {
-        warning("Confusing minuses.");
-        this.right = expression(150);
-        this.arity = 'unary';
-        return this;
-    });
-    infix('---', function (left) {
-        warning("Confusing minuses.");
-        this.left = left;
-        this.right = expression(130);
-        return this;
-    }, 130);
-    infix('*', 'mult', 140);
-    infix('/', 'div', 140);
-    infix('%', 'mod', 140);
-
-    suffix('++', 'postinc');
-    prefix('++', 'preinc');
-    syntax['++'].exps = true;
-
-    suffix('--', 'postdec');
-    prefix('--', 'predec');
-    syntax['--'].exps = true;
-    prefix('delete', function () {
-        var p = expression(0);
-        if (!p || (p.id !== '.' && p.id !== '[')) {
-            warning("Variables should not be deleted.");
-        }
-        this.first = p;
-        return this;
-    }).exps = true;
-
-    prefix('~', function () {
-        if (option.bitwise) {
-            warning("Unexpected '{a}'.", this, '~');
-        }
-        expression(150);
-        return this;
-    });
-
-    prefix('!', function () {
-        this.right = expression(150);
-        this.arity = 'unary';
-        if (bang[this.right.id] === true) {
-            warning("Confusing use of '{a}'.", this, '!');
-        }
-        return this;
-    });
-    prefix('typeof', 'typeof');
-    prefix('new', function () {
-        var c = expression(155), i;
-        if (c && c.id !== 'function') {
-            if (c.identifier) {
-                c['new'] = true;
-                switch (c.value) {
-                case 'Object':
-                    warning("Use the object literal notation {}.", token);
-                    break;
-                case 'Number':
-                case 'String':
-                case 'Boolean':
-                case 'Math':
-                case 'JSON':
-                    warning("Do not use {a} as a constructor.", token, c.value);
-                    break;
-                case 'Function':
-                    if (!option.evil) {
-                        warning("The Function constructor is eval.");
-                    }
-                    break;
-                case 'Date':
-                case 'RegExp':
-                    break;
-                default:
-                    if (c.id !== 'function') {
-                        i = c.value.substr(0, 1);
-                        if (option.newcap && (i < 'A' || i > 'Z')) {
-                            warning("A constructor name should start with an uppercase letter.",
-                                token);
-                        }
-                    }
-                }
-            } else {
-                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
-                    warning("Bad constructor.", token);
-                }
-            }
-        } else {
-            if (!option.supernew)
-                warning("Weird construction. Delete 'new'.", this);
-        }
-        adjacent(token, nexttoken);
-        if (nexttoken.id !== '(' && !option.supernew) {
-            warning("Missing '()' invoking a constructor.");
-        }
-        this.first = c;
-        return this;
-    });
-    syntax['new'].exps = true;
-
-    prefix('void').exps = true;
-
-    infix('.', function (left, that) {
-        adjacent(prevtoken, token);
-        nobreak();
-        var m = identifier();
-        if (typeof m === 'string') {
-            countMember(m);
-        }
-        that.left = left;
-        that.right = m;
-        if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) {
-            if (option.noarg)
-                warning("Avoid arguments.{a}.", left, m);
-            else if (directive['use strict'])
-                error('Strict violation.');
-        } else if (!option.evil && left && left.value === 'document' &&
-                (m === 'write' || m === 'writeln')) {
-            warning("document.write can be a form of eval.", left);
-        }
-        if (!option.evil && (m === 'eval' || m === 'execScript')) {
-            warning('eval is evil.');
-        }
-        return that;
-    }, 160, true);
-
-    infix('(', function (left, that) {
-        if (prevtoken.id !== '}' && prevtoken.id !== ')') {
-            nobreak(prevtoken, token);
-        }
-        nospace();
-        if (option.immed && !left.immed && left.id === 'function') {
-            warning("Wrap an immediate function invocation in parentheses " +
-                "to assist the reader in understanding that the expression " +
-                "is the result of a function, and not the function itself.");
-        }
-        var n = 0,
-            p = [];
-        if (left) {
-            if (left.type === '(identifier)') {
-                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
-                    if (left.value !== 'Number' && left.value !== 'String' &&
-                            left.value !== 'Boolean' &&
-                            left.value !== 'Date') {
-                        if (left.value === 'Math') {
-                            warning("Math is not a function.", left);
-                        } else if (option.newcap) {
-                            warning(
-"Missing 'new' prefix when invoking a constructor.", left);
-                        }
-                    }
-                }
-            }
-        }
-        if (nexttoken.id !== ')') {
-            for (;;) {
-                p[p.length] = expression(10);
-                n += 1;
-                if (nexttoken.id !== ',') {
-                    break;
-                }
-                comma();
-            }
-        }
-        advance(')');
-        nospace(prevtoken, token);
-        if (typeof left === 'object') {
-            if (left.value === 'parseInt' && n === 1) {
-                warning("Missing radix parameter.", left);
-            }
-            if (!option.evil) {
-                if (left.value === 'eval' || left.value === 'Function' ||
-                        left.value === 'execScript') {
-                    warning("eval is evil.", left);
-                } else if (p[0] && p[0].id === '(string)' &&
-                       (left.value === 'setTimeout' ||
-                        left.value === 'setInterval')) {
-                    warning(
-    "Implied eval is evil. Pass a function instead of a string.", left);
-                }
-            }
-            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
-                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
-                    left.id !== '?') {
-                warning("Bad invocation.", left);
-            }
-        }
-        that.left = left;
-        return that;
-    }, 155, true).exps = true;
-
-    prefix('(', function () {
-        nospace();
-        if (nexttoken.id === 'function') {
-            nexttoken.immed = true;
-        }
-        var v = expression(0);
-        advance(')', this);
-        nospace(prevtoken, token);
-        if (option.immed && v.id === 'function') {
-            if (nexttoken.id === '(') {
-                warning(
-"Move the invocation into the parens that contain the function.", nexttoken);
-            } else {
-                warning(
-"Do not wrap function literals in parens unless they are to be immediately invoked.",
-                        this);
-            }
-        }
-        return v;
-    });
-
-    infix('[', function (left, that) {
-        nobreak(prevtoken, token);
-        nospace();
-        var e = expression(0), s;
-        if (e && e.type === '(string)') {
-            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {
-                warning("eval is evil.", that);
-            }
-            countMember(e.value);
-            if (!option.sub && ix.test(e.value)) {
-                s = syntax[e.value];
-                if (!s || !s.reserved) {
-                    warning("['{a}'] is better written in dot notation.",
-                            e, e.value);
-                }
-            }
-        }
-        advance(']', that);
-        nospace(prevtoken, token);
-        that.left = left;
-        that.right = e;
-        return that;
-    }, 160, true);
-
-    prefix('[', function () {
-        var b = token.line !== nexttoken.line;
-        this.first = [];
-        if (b) {
-            indent += option.indent;
-            if (nexttoken.from === indent + option.indent) {
-                indent += option.indent;
-            }
-        }
-        while (nexttoken.id !== '(end)') {
-            while (nexttoken.id === ',') {
-                warning("Extra comma.");
-                advance(',');
-            }
-            if (nexttoken.id === ']') {
-                break;
-            }
-            if (b && token.line !== nexttoken.line) {
-                indentation();
-            }
-            this.first.push(expression(10));
-            if (nexttoken.id === ',') {
-                comma();
-                if (nexttoken.id === ']' && !option.es5) {
-                    warning("Extra comma.", token);
-                    break;
-                }
-            } else {
-                break;
-            }
-        }
-        if (b) {
-            indent -= option.indent;
-            indentation();
-        }
-        advance(']', this);
-        return this;
-    }, 160);
-
-
-    function property_name() {
-        var id = optionalidentifier(true);
-        if (!id) {
-            if (nexttoken.id === '(string)') {
-                id = nexttoken.value;
-                advance();
-            } else if (nexttoken.id === '(number)') {
-                id = nexttoken.value.toString();
-                advance();
-            }
-        }
-        return id;
-    }
-
-
-    function functionparams() {
-        var i, t = nexttoken, p = [];
-        advance('(');
-        nospace();
-        if (nexttoken.id === ')') {
-            advance(')');
-            return;
-        }
-        for (;;) {
-            i = identifier(true);
-            p.push(i);
-            addlabel(i, 'parameter');
-            if (nexttoken.id === ',') {
-                comma();
-            } else {
-                advance(')', t);
-                nospace(prevtoken, token);
-                return p;
-            }
-        }
-    }
-
-
-    function doFunction(i, statement) {
-        var f,
-            oldOption = option,
-            oldScope  = scope;
-
-        option = Object.create(option);
-        scope = Object.create(scope);
-
-        funct = {
-            '(name)'     : i || '"' + anonname + '"',
-            '(line)'     : nexttoken.line,
-            '(context)'  : funct,
-            '(breakage)' : 0,
-            '(loopage)'  : 0,
-            '(scope)'    : scope,
-            '(statement)': statement
-        };
-        f = funct;
-        token.funct = funct;
-        functions.push(funct);
-        if (i) {
-            addlabel(i, 'function');
-        }
-        funct['(params)'] = functionparams();
-
-        block(false, false, true);
-        scope = oldScope;
-        option = oldOption;
-        funct['(last)'] = token.line;
-        funct = funct['(context)'];
-        return f;
-    }
-
-
-    (function (x) {
-        x.nud = function () {
-            var b, f, i, j, p, seen = {}, t;
-            var prop, acc = {}; // Accessor methods
-
-            function saveSetter(name, token) {
-                if (!acc[name]) {
-                    acc[name] = {};
-                }
-                acc[name].setter = true;
-                acc[name].setterToken = token;
-            }
-
-            function saveGetter(name) {
-                if (!acc[name]) {
-                    acc[name] = {};
-                }
-                acc[name].getter = true;
-            }
-
-            b = token.line !== nexttoken.line;
-            if (b) {
-                indent += option.indent;
-                if (nexttoken.from === indent + option.indent) {
-                    indent += option.indent;
-                }
-            }
-            for (;;) {
-                if (nexttoken.id === '}') {
-                    break;
-                }
-                if (b) {
-                    indentation();
-                }
-                if (nexttoken.value === 'get' && peek().id !== ':') {
-                    advance('get');
-                    if (!option.es5) {
-                        error("get/set are ES5 features.");
-                    }
-                    i = property_name();
-                    if (!i) {
-                        error("Missing property name.");
-                    }
-                    saveGetter(i);
-                    t = nexttoken;
-                    adjacent(token, nexttoken);
-                    f = doFunction();
-                    if (!option.loopfunc && funct['(loopage)']) {
-                        warning("Don't make functions within a loop.", t);
-                    }
-                    p = f['(params)'];
-                    if (p) {
-                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);
-                    }
-                    adjacent(token, nexttoken);
-                } else if (nexttoken.value === 'set' && peek().id !== ':') {
-                    advance('set');
-                    if (!option.es5) {
-                        error("get/set are ES5 features.");
-                    }
-                    i = property_name();
-                    if (!i) {
-                        error("Missing property name.");
-                    }
-                    if (acc[i] && acc[i].setter) {
-                        warning("Duplicate member '{a}'.", nexttoken, i);
-                    }
-                    saveSetter(i, nexttoken);
-                    seen[i] = false;
-                    t = nexttoken;
-                    adjacent(token, nexttoken);
-                    f = doFunction();
-                    p = f['(params)'];
-                    if (!p || p.length !== 1 || p[0] !== 'value') {
-                        warning("Expected (value) in set {a} function.", t, i);
-                    }
-                } else {
-                    i = property_name();
-                    if (typeof i !== 'string') {
-                        break;
-                    }
-                    advance(':');
-                    nonadjacent(token, nexttoken);
-                    expression(10);
-                }
-                if (seen[i] === true) {
-                    warning("Duplicate member '{a}'.", nexttoken, i);
-                }
-                seen[i] = true;
-                countMember(i);
-                if (nexttoken.id === ',') {
-                    comma();
-                    if (nexttoken.id === ',') {
-                        warning("Extra comma.", token);
-                    } else if (nexttoken.id === '}' && !option.es5) {
-                        warning("Extra comma.", token);
-                    }
-                } else {
-                    break;
-                }
-            }
-            if (b) {
-                indent -= option.indent;
-                indentation();
-            }
-            advance('}', this);
-
-            // Check for lonely setters if in the ES5 mode.
-            if (option.es5) {
-                for (prop in acc) {
-                    if (acc.hasOwnProperty(prop) && acc[prop].setter && !acc[prop].getter) {
-                        warning("Setter is defined without getter.", acc[prop].setterToken);
-                    }
-                }
-            }
-            return this;
-        };
-        x.fud = function () {
-            error("Expected to see a statement and instead saw a block.", token);
-        };
-    }(delim('{')));
-
-// This Function is called when esnext option is set to true
-// it adds the `const` statement to JSHINT
-
-    useESNextSyntax = function () {
-        var conststatement = stmt('const', function (prefix) {
-            var id, name, value;
-
-            this.first = [];
-            for (;;) {
-                nonadjacent(token, nexttoken);
-                id = identifier();
-                if (funct[id] === "const") {
-                    warning("const '" + id + "' has already been declared");
-                }
-                if (funct['(global)'] && predefined[id] === false) {
-                    warning("Redefinition of '{a}'.", token, id);
-                }
-                addlabel(id, 'const');
-                if (prefix) {
-                    break;
-                }
-                name = token;
-                this.first.push(token);
-
-                if (nexttoken.id !== "=") {
-                    warning("const " +
-                      "'{a}' is initialized to 'undefined'.", token, id);
-                }
-
-                if (nexttoken.id === '=') {
-                    nonadjacent(token, nexttoken);
-                    advance('=');
-                    nonadjacent(token, nexttoken);
-                    if (nexttoken.id === 'undefined') {
-                        warning("It is not necessary to initialize " +
-                          "'{a}' to 'undefined'.", token, id);
-                    }
-                    if (peek(0).id === '=' && nexttoken.identifier) {
-                        error("Constant {a} was not declared correctly.",
-                                nexttoken, nexttoken.value);
-                    }
-                    value = expression(0);
-                    name.first = value;
-                }
-
-                if (nexttoken.id !== ',') {
-                    break;
-                }
-                comma();
-            }
-            return this;
-        });
-        conststatement.exps = true;
-    };
-
-    var varstatement = stmt('var', function (prefix) {
-        // JavaScript does not have block scope. It only has function scope. So,
-        // declaring a variable in a block can have unexpected consequences.
-        var id, name, value;
-
-        if (funct['(onevar)'] && option.onevar) {
-            warning("Too many var statements.");
-        } else if (!funct['(global)']) {
-            funct['(onevar)'] = true;
-        }
-        this.first = [];
-        for (;;) {
-            nonadjacent(token, nexttoken);
-            id = identifier();
-            if (option.esnext && funct[id] === "const") {
-                warning("const '" + id + "' has already been declared");
-            }
-            if (funct['(global)'] && predefined[id] === false) {
-                warning("Redefinition of '{a}'.", token, id);
-            }
-            addlabel(id, 'unused');
-            if (prefix) {
-                break;
-            }
-            name = token;
-            this.first.push(token);
-            if (nexttoken.id === '=') {
-                nonadjacent(token, nexttoken);
-                advance('=');
-                nonadjacent(token, nexttoken);
-                if (nexttoken.id === 'undefined') {
-                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
-                }
-                if (peek(0).id === '=' && nexttoken.identifier) {
-                    error("Variable {a} was not declared correctly.",
-                            nexttoken, nexttoken.value);
-                }
-                value = expression(0);
-                name.first = value;
-            }
-            if (nexttoken.id !== ',') {
-                break;
-            }
-            comma();
-        }
-        return this;
-    });
-    varstatement.exps = true;
-
-    blockstmt('function', function () {
-        if (inblock) {
-            warning("Function declarations should not be placed in blocks. " +
-                "Use a function expression or move the statement to the top of " +
-                "the outer function.", token);
-
-        }
-        var i = identifier();
-        if (option.esnext && funct[i] === "const") {
-            warning("const '" + i + "' has already been declared");
-        }
-        adjacent(token, nexttoken);
-        addlabel(i, 'unction');
-        doFunction(i, true);
-        if (nexttoken.id === '(' && nexttoken.line === token.line) {
-            error(
-"Function declarations are not invocable. Wrap the whole function invocation in parens.");
-        }
-        return this;
-    });
-
-    prefix('function', function () {
-        var i = optionalidentifier();
-        if (i) {
-            adjacent(token, nexttoken);
-        } else {
-            nonadjacent(token, nexttoken);
-        }
-        doFunction(i);
-        if (!option.loopfunc && funct['(loopage)']) {
-            warning("Don't make functions within a loop.");
-        }
-        return this;
-    });
-
-    blockstmt('if', function () {
-        var t = nexttoken;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        expression(20);
-        if (nexttoken.id === '=') {
-            if (!option.boss)
-                warning("Expected a conditional expression and instead saw an assignment.");
-            advance('=');
-            expression(20);
-        }
-        advance(')', t);
-        nospace(prevtoken, token);
-        block(true, true);
-        if (nexttoken.id === 'else') {
-            nonadjacent(token, nexttoken);
-            advance('else');
-            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
-                statement(true);
-            } else {
-                block(true, true);
-            }
-        }
-        return this;
-    });
-
-    blockstmt('try', function () {
-        var b, e, s;
-
-        block(false);
-        if (nexttoken.id === 'catch') {
-            advance('catch');
-            nonadjacent(token, nexttoken);
-            advance('(');
-            s = scope;
-            scope = Object.create(s);
-            e = nexttoken.value;
-            if (nexttoken.type !== '(identifier)') {
-                warning("Expected an identifier and instead saw '{a}'.",
-                    nexttoken, e);
-            } else {
-                addlabel(e, 'exception');
-            }
-            advance();
-            advance(')');
-            block(false);
-            b = true;
-            scope = s;
-        }
-        if (nexttoken.id === 'finally') {
-            advance('finally');
-            block(false);
-            return;
-        } else if (!b) {
-            error("Expected '{a}' and instead saw '{b}'.",
-                    nexttoken, 'catch', nexttoken.value);
-        }
-        return this;
-    });
-
-    blockstmt('while', function () {
-        var t = nexttoken;
-        funct['(breakage)'] += 1;
-        funct['(loopage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        expression(20);
-        if (nexttoken.id === '=') {
-            if (!option.boss)
-                warning("Expected a conditional expression and instead saw an assignment.");
-            advance('=');
-            expression(20);
-        }
-        advance(')', t);
-        nospace(prevtoken, token);
-        block(true, true);
-        funct['(breakage)'] -= 1;
-        funct['(loopage)'] -= 1;
-        return this;
-    }).labelled = true;
-
-    reserve('with');
-
-    blockstmt('switch', function () {
-        var t = nexttoken,
-            g = false;
-        funct['(breakage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        this.condition = expression(20);
-        advance(')', t);
-        nospace(prevtoken, token);
-        nonadjacent(token, nexttoken);
-        t = nexttoken;
-        advance('{');
-        nonadjacent(token, nexttoken);
-        indent += option.indent;
-        this.cases = [];
-        for (;;) {
-            switch (nexttoken.id) {
-            case 'case':
-                switch (funct['(verb)']) {
-                case 'break':
-                case 'case':
-                case 'continue':
-                case 'return':
-                case 'switch':
-                case 'throw':
-                    break;
-                default:
-                    // You can tell JSHint that you don't use break intentionally by
-                    // adding a comment /* falls through */ on a line just before
-                    // the next `case`.
-                    if (!ft.test(lines[nexttoken.line - 2])) {
-                        warning(
-                            "Expected a 'break' statement before 'case'.",
-                            token);
-                    }
-                }
-                indentation(-option.indent);
-                advance('case');
-                this.cases.push(expression(20));
-                g = true;
-                advance(':');
-                funct['(verb)'] = 'case';
-                break;
-            case 'default':
-                switch (funct['(verb)']) {
-                case 'break':
-                case 'continue':
-                case 'return':
-                case 'throw':
-                    break;
-                default:
-                    if (!ft.test(lines[nexttoken.line - 2])) {
-                        warning(
-                            "Expected a 'break' statement before 'default'.",
-                            token);
-                    }
-                }
-                indentation(-option.indent);
-                advance('default');
-                g = true;
-                advance(':');
-                break;
-            case '}':
-                indent -= option.indent;
-                indentation();
-                advance('}', t);
-                if (this.cases.length === 1 || this.condition.id === 'true' ||
-                        this.condition.id === 'false') {
-                    if (!option.onecase)
-                        warning("This 'switch' should be an 'if'.", this);
-                }
-                funct['(breakage)'] -= 1;
-                funct['(verb)'] = undefined;
-                return;
-            case '(end)':
-                error("Missing '{a}'.", nexttoken, '}');
-                return;
-            default:
-                if (g) {
-                    switch (token.id) {
-                    case ',':
-                        error("Each value should have its own case label.");
-                        return;
-                    case ':':
-                        g = false;
-                        statements();
-                        break;
-                    default:
-                        error("Missing ':' on a case clause.", token);
-                        return;
-                    }
-                } else {
-                    if (token.id === ':') {
-                        advance(':');
-                        error("Unexpected '{a}'.", token, ':');
-                        statements();
-                    } else {
-                        error("Expected '{a}' and instead saw '{b}'.",
-                            nexttoken, 'case', nexttoken.value);
-                        return;
-                    }
-                }
-            }
-        }
-    }).labelled = true;
-
-    stmt('debugger', function () {
-        if (!option.debug) {
-            warning("All 'debugger' statements should be removed.");
-        }
-        return this;
-    }).exps = true;
-
-    (function () {
-        var x = stmt('do', function () {
-            funct['(breakage)'] += 1;
-            funct['(loopage)'] += 1;
-            this.first = block(true);
-            advance('while');
-            var t = nexttoken;
-            nonadjacent(token, t);
-            advance('(');
-            nospace();
-            expression(20);
-            if (nexttoken.id === '=') {
-                if (!option.boss)
-                    warning("Expected a conditional expression and instead saw an assignment.");
-                advance('=');
-                expression(20);
-            }
-            advance(')', t);
-            nospace(prevtoken, token);
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        });
-        x.labelled = true;
-        x.exps = true;
-    }());
-
-    blockstmt('for', function () {
-        var s, t = nexttoken;
-        funct['(breakage)'] += 1;
-        funct['(loopage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
-            if (nexttoken.id === 'var') {
-                advance('var');
-                varstatement.fud.call(varstatement, true);
-            } else {
-                switch (funct[nexttoken.value]) {
-                case 'unused':
-                    funct[nexttoken.value] = 'var';
-                    break;
-                case 'var':
-                    break;
-                default:
-                    warning("Bad for in variable '{a}'.",
-                            nexttoken, nexttoken.value);
-                }
-                advance();
-            }
-            advance('in');
-            expression(20);
-            advance(')', t);
-            s = block(true, true);
-            if (option.forin && s && (s.length > 1 || typeof s[0] !== 'object' ||
-                    s[0].value !== 'if')) {
-                warning("The body of a for in should be wrapped in an if statement to filter " +
-                        "unwanted properties from the prototype.", this);
-            }
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        } else {
-            if (nexttoken.id !== ';') {
-                if (nexttoken.id === 'var') {
-                    advance('var');
-                    varstatement.fud.call(varstatement);
-                } else {
-                    for (;;) {
-                        expression(0, 'for');
-                        if (nexttoken.id !== ',') {
-                            break;
-                        }
-                        comma();
-                    }
-                }
-            }
-            nolinebreak(token);
-            advance(';');
-            if (nexttoken.id !== ';') {
-                expression(20);
-                if (nexttoken.id === '=') {
-                    if (!option.boss)
-                        warning("Expected a conditional expression and instead saw an assignment.");
-                    advance('=');
-                    expression(20);
-                }
-            }
-            nolinebreak(token);
-            advance(';');
-            if (nexttoken.id === ';') {
-                error("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, ')', ';');
-            }
-            if (nexttoken.id !== ')') {
-                for (;;) {
-                    expression(0, 'for');
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    comma();
-                }
-            }
-            advance(')', t);
-            nospace(prevtoken, token);
-            block(true, true);
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        }
-    }).labelled = true;
-
-
-    stmt('break', function () {
-        var v = nexttoken.value;
-
-        if (funct['(breakage)'] === 0)
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-
-        if (!option.asi)
-            nolinebreak(this);
-
-        if (nexttoken.id !== ';') {
-            if (token.line === nexttoken.line) {
-                if (funct[v] !== 'label') {
-                    warning("'{a}' is not a statement label.", nexttoken, v);
-                } else if (scope[v] !== funct) {
-                    warning("'{a}' is out of scope.", nexttoken, v);
-                }
-                this.first = nexttoken;
-                advance();
-            }
-        }
-        reachable('break');
-        return this;
-    }).exps = true;
-
-
-    stmt('continue', function () {
-        var v = nexttoken.value;
-
-        if (funct['(breakage)'] === 0)
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-
-        if (!option.asi)
-            nolinebreak(this);
-
-        if (nexttoken.id !== ';') {
-            if (token.line === nexttoken.line) {
-                if (funct[v] !== 'label') {
-                    warning("'{a}' is not a statement label.", nexttoken, v);
-                } else if (scope[v] !== funct) {
-                    warning("'{a}' is out of scope.", nexttoken, v);
-                }
-                this.first = nexttoken;
-                advance();
-            }
-        } else if (!funct['(loopage)']) {
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-        }
-        reachable('continue');
-        return this;
-    }).exps = true;
-
-
-    stmt('return', function () {
-        if (this.line === nexttoken.line) {
-            if (nexttoken.id === '(regexp)')
-                warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
-
-            if (nexttoken.id !== ';' && !nexttoken.reach) {
-                nonadjacent(token, nexttoken);
-                if (peek().value === "=" && !option.boss) {
-                    warningAt("Did you mean to return a conditional instead of an assignment?",
-                              token.line, token.character + 1);
-                }
-                this.first = expression(0);
-            }
-        } else if (!option.asi) {
-            nolinebreak(this); // always warn (Line breaking error)
-        }
-        reachable('return');
-        return this;
-    }).exps = true;
-
-
-    stmt('throw', function () {
-        nolinebreak(this);
-        nonadjacent(token, nexttoken);
-        this.first = expression(20);
-        reachable('throw');
-        return this;
-    }).exps = true;
-
-//  Superfluous reserved words
-
-    reserve('class');
-    reserve('const');
-    reserve('enum');
-    reserve('export');
-    reserve('extends');
-    reserve('import');
-    reserve('super');
-
-    reserve('let');
-    reserve('yield');
-    reserve('implements');
-    reserve('interface');
-    reserve('package');
-    reserve('private');
-    reserve('protected');
-    reserve('public');
-    reserve('static');
-
-
-// Parse JSON
-
-    function jsonValue() {
-
-        function jsonObject() {
-            var o = {}, t = nexttoken;
-            advance('{');
-            if (nexttoken.id !== '}') {
-                for (;;) {
-                    if (nexttoken.id === '(end)') {
-                        error("Missing '}' to match '{' from line {a}.",
-                                nexttoken, t.line);
-                    } else if (nexttoken.id === '}') {
-                        warning("Unexpected comma.", token);
-                        break;
-                    } else if (nexttoken.id === ',') {
-                        error("Unexpected comma.", nexttoken);
-                    } else if (nexttoken.id !== '(string)') {
-                        warning("Expected a string and instead saw {a}.",
-                                nexttoken, nexttoken.value);
-                    }
-                    if (o[nexttoken.value] === true) {
-                        warning("Duplicate key '{a}'.",
-                                nexttoken, nexttoken.value);
-                    } else if ((nexttoken.value === '__proto__' &&
-                        !option.proto) || (nexttoken.value === '__iterator__' &&
-                        !option.iterator)) {
-                        warning("The '{a}' key may produce unexpected results.",
-                            nexttoken, nexttoken.value);
-                    } else {
-                        o[nexttoken.value] = true;
-                    }
-                    advance();
-                    advance(':');
-                    jsonValue();
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    advance(',');
-                }
-            }
-            advance('}');
-        }
-
-        function jsonArray() {
-            var t = nexttoken;
-            advance('[');
-            if (nexttoken.id !== ']') {
-                for (;;) {
-                    if (nexttoken.id === '(end)') {
-                        error("Missing ']' to match '[' from line {a}.",
-                                nexttoken, t.line);
-                    } else if (nexttoken.id === ']') {
-                        warning("Unexpected comma.", token);
-                        break;
-                    } else if (nexttoken.id === ',') {
-                        error("Unexpected comma.", nexttoken);
-                    }
-                    jsonValue();
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    advance(',');
-                }
-            }
-            advance(']');
-        }
-
-        switch (nexttoken.id) {
-        case '{':
-            jsonObject();
-            break;
-        case '[':
-            jsonArray();
-            break;
-        case 'true':
-        case 'false':
-        case 'null':
-        case '(number)':
-        case '(string)':
-            advance();
-            break;
-        case '-':
-            advance('-');
-            if (token.character !== nexttoken.from) {
-                warning("Unexpected space after '-'.", token);
-            }
-            adjacent(token, nexttoken);
-            advance('(number)');
-            break;
-        default:
-            error("Expected a JSON value.", nexttoken);
-        }
-    }
-
-
-// The actual JSHINT function itself.
-
-    var itself = function (s, o, g) {
-        var a, i, k;
-        JSHINT.errors = [];
-        JSHINT.undefs = [];
-        predefined = Object.create(standard);
-        combine(predefined, g || {});
-        if (o) {
-            a = o.predef;
-            if (a) {
-                if (Array.isArray(a)) {
-                    for (i = 0; i < a.length; i += 1) {
-                        predefined[a[i]] = true;
-                    }
-                } else if (typeof a === 'object') {
-                    k = Object.keys(a);
-                    for (i = 0; i < k.length; i += 1) {
-                        predefined[k[i]] = !!a[k[i]];
-                    }
-                }
-            }
-            option = o;
-        } else {
-            option = {};
-        }
-        option.indent = option.indent || 4;
-        option.maxerr = option.maxerr || 50;
-
-        tab = '';
-        for (i = 0; i < option.indent; i += 1) {
-            tab += ' ';
-        }
-        indent = 1;
-        global = Object.create(predefined);
-        scope = global;
-        funct = {
-            '(global)': true,
-            '(name)': '(global)',
-            '(scope)': scope,
-            '(breakage)': 0,
-            '(loopage)': 0
-        };
-        functions = [funct];
-        urls = [];
-        stack = null;
-        member = {};
-        membersOnly = null;
-        implied = {};
-        inblock = false;
-        lookahead = [];
-        jsonmode = false;
-        warnings = 0;
-        lex.init(s);
-        prereg = true;
-        directive = {};
-
-        prevtoken = token = nexttoken = syntax['(begin)'];
-        assume();
-
-        // combine the passed globals after we've assumed all our options
-        combine(predefined, g || {});
-
-        try {
-            advance();
-            switch (nexttoken.id) {
-            case '{':
-            case '[':
-                option.laxbreak = true;
-                jsonmode = true;
-                jsonValue();
-                break;
-            default:
-                directives();
-                if (directive["use strict"] && !option.globalstrict) {
-                    warning("Use the function form of \"use strict\".", prevtoken);
-                }
-
-                statements();
-            }
-            advance('(end)');
-        } catch (e) {
-            if (e) {
-                var nt = nexttoken || {};
-                JSHINT.errors.push({
-                    raw       : e.raw,
-                    reason    : e.message,
-                    line      : e.line || nt.line,
-                    character : e.character || nt.from
-                }, null);
-            }
-        }
-
-        for (i = 0; i < JSHINT.undefs.length; i += 1) {
-            k = JSHINT.undefs[i].slice(0);
-            scope = k.shift();
-            a = k[2];
-
-            if (typeof scope[a] !== 'string' && typeof funct[a] !== 'string') {
-                warning.apply(warning, k);
-            }
-        }
-
-        return JSHINT.errors.length === 0;
-    };
-
-    // Data summary.
-    itself.data = function () {
-
-        var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j,
-            members = [], n, unused = [], v;
-        if (itself.errors.length) {
-            data.errors = itself.errors;
-        }
-
-        if (jsonmode) {
-            data.json = true;
-        }
-
-        for (n in implied) {
-            if (is_own(implied, n)) {
-                implieds.push({
-                    name: n,
-                    line: implied[n]
-                });
-            }
-        }
-        if (implieds.length > 0) {
-            data.implieds = implieds;
-        }
-
-        if (urls.length > 0) {
-            data.urls = urls;
-        }
-
-        globals = Object.keys(scope);
-        if (globals.length > 0) {
-            data.globals = globals;
-        }
-
-        for (i = 1; i < functions.length; i += 1) {
-            f = functions[i];
-            fu = {};
-            for (j = 0; j < functionicity.length; j += 1) {
-                fu[functionicity[j]] = [];
-            }
-            for (n in f) {
-                if (is_own(f, n) && n.charAt(0) !== '(') {
-                    v = f[n];
-                    if (v === 'unction') {
-                        v = 'unused';
-                    }
-                    if (Array.isArray(fu[v])) {
-                        fu[v].push(n);
-                        if (v === 'unused') {
-                            unused.push({
-                                name: n,
-                                line: f['(line)'],
-                                'function': f['(name)']
-                            });
-                        }
-                    }
-                }
-            }
-            for (j = 0; j < functionicity.length; j += 1) {
-                if (fu[functionicity[j]].length === 0) {
-                    delete fu[functionicity[j]];
-                }
-            }
-            fu.name = f['(name)'];
-            fu.param = f['(params)'];
-            fu.line = f['(line)'];
-            fu.last = f['(last)'];
-            data.functions.push(fu);
-        }
-
-        if (unused.length > 0) {
-            data.unused = unused;
-        }
-
-        members = [];
-        for (n in member) {
-            if (typeof member[n] === 'number') {
-                data.member = member;
-                break;
-            }
-        }
-
-        return data;
-    };
-
-    itself.report = function (option) {
-        var data = itself.data();
-
-        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
-
-        function detail(h, array) {
-            var b, i, singularity;
-            if (array) {
-                o.push('<div><i>' + h + '</i> ');
-                array = array.sort();
-                for (i = 0; i < array.length; i += 1) {
-                    if (array[i] !== singularity) {
-                        singularity = array[i];
-                        o.push((b ? ', ' : '') + singularity);
-                        b = true;
-                    }
-                }
-                o.push('</div>');
-            }
-        }
-
-
-        if (data.errors || data.implieds || data.unused) {
-            err = true;
-            o.push('<div id=errors><i>Error:</i>');
-            if (data.errors) {
-                for (i = 0; i < data.errors.length; i += 1) {
-                    c = data.errors[i];
-                    if (c) {
-                        e = c.evidence || '';
-                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
-                                c.line + ' character ' + c.character : '') +
-                                ': ' + c.reason.entityify() +
-                                '</p><p class=evidence>' +
-                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
-                                e).entityify()) + '</p>');
-                    }
-                }
-            }
-
-            if (data.implieds) {
-                s = [];
-                for (i = 0; i < data.implieds.length; i += 1) {
-                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
-                        data.implieds[i].line + '</i>';
-                }
-                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
-            }
-
-            if (data.unused) {
-                s = [];
-                for (i = 0; i < data.unused.length; i += 1) {
-                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
-                        data.unused[i].line + '</i> <code>' +
-                        data.unused[i]['function'] + '</code>';
-                }
-                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
-            }
-            if (data.json) {
-                o.push('<p>JSON: bad.</p>');
-            }
-            o.push('</div>');
-        }
-
-        if (!option) {
-
-            o.push('<br><div id=functions>');
-
-            if (data.urls) {
-                detail("URLs<br>", data.urls, '<br>');
-            }
-
-            if (data.json && !err) {
-                o.push('<p>JSON: good.</p>');
-            } else if (data.globals) {
-                o.push('<div><i>Global</i> ' +
-                        data.globals.sort().join(', ') + '</div>');
-            } else {
-                o.push('<div><i>No new global variables introduced.</i></div>');
-            }
-
-            for (i = 0; i < data.functions.length; i += 1) {
-                f = data.functions[i];
-
-                o.push('<br><div class=function><i>' + f.line + '-' +
-                        f.last + '</i> ' + (f.name || '') + '(' +
-                        (f.param ? f.param.join(', ') : '') + ')</div>');
-                detail('<big><b>Unused</b></big>', f.unused);
-                detail('Closure', f.closure);
-                detail('Variable', f['var']);
-                detail('Exception', f.exception);
-                detail('Outer', f.outer);
-                detail('Global', f.global);
-                detail('Label', f.label);
-            }
-
-            if (data.member) {
-                a = Object.keys(data.member);
-                if (a.length) {
-                    a = a.sort();
-                    m = '<br><pre id=members>/*members ';
-                    l = 10;
-                    for (i = 0; i < a.length; i += 1) {
-                        k = a[i];
-                        n = k.name();
-                        if (l + n.length > 72) {
-                            o.push(m + '<br>');
-                            m = '    ';
-                            l = 1;
-                        }
-                        l += n.length + 2;
-                        if (data.member[k] === 1) {
-                            n = '<i>' + n + '</i>';
-                        }
-                        if (i < a.length - 1) {
-                            n += ', ';
-                        }
-                        m += n;
-                    }
-                    o.push(m + '<br>*/</pre>');
-                }
-                o.push('</div>');
-            }
-        }
-        return o.join('');
-    };
-
-    itself.jshint = itself;
-    itself.edition = '2011-04-16';
-
-    return itself;
-}());
-
-// Make JSHINT a Node module, if possible.
-if (typeof exports === 'object' && exports)
-    exports.JSHINT = JSHINT;
-/*jshint boss: true, rhino: true */
-/*globals JSHINT*/
-
-(function (args) {
-    var filenames = [],
-        optstr, // arg1=val1,arg2=val2,...
-        predef, // global1=override,global2,global3,...
-        opts   = { rhino: true },
-        retval = 0;
-
-    args.forEach(function (arg) {
-        if (arg.indexOf("=") > -1) {
-            //first time it's the options
-            if (!optstr) {
-                optstr = arg;
-            } else if (!predef) {
-                predef = arg;
-            }
-        } else {
-            filenames.push(arg);
-        }
-    });
-
-    if (filenames.length === 0) {
-        print('Usage: jshint.js file.js');
-        quit(1);
-    }
-
-    if (optstr) {
-        optstr.split(',').forEach(function (arg) {
-            var o = arg.split('=');
-            opts[o[0]] = (function (ov) {
-                switch (ov) {
-                case 'true':
-                    return true;
-                case 'false':
-                    return false;
-                default:
-                    return ov;
-                }
-            }(o[1]));
-        });
-    }
-
-    if (predef) {
-        opts.predef = {};
-        predef.split(',').forEach(function (arg) {
-            var global = arg.split('=');
-            opts.predef[global[0]] = (function (override) {
-                return (override === 'false') ? false : true;
-            }(global[1]));
-        });
-    }
-
-    filenames.forEach(function (name) {
-
-        var input = readFile(name);
-
-        if (!input) {
-            print('jshint: Couldn\'t open file ' + name);
-            quit(1);
-        }
-
-        if (!JSHINT(input, opts)) {
-            for (var i = 0, err; err = JSHINT.errors[i]; i += 1) {
-                print(err.reason + ' (' + name + ':' + err.line + ':' + err.character + ')');
-                print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
-                print('');
-            }
-            retval = 1;
-        }
-    });
-
-    quit(retval);
-}(arguments));
diff --git a/jshint/env/jsc.js b/jshint/env/jsc.js
deleted file mode 100644 (file)
index 94799c9..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-/*jshint boss:true, evil:true */
-
-// usage:
-//   jsc ${env_home}/jsc.js -- ${file} "$(cat ${file})" "{option1:true,option2:false} ${env_home}"
-var env_home = '';
-if (arguments.length > 3) {
-  env_home = arguments[3].toString().replace(/\/env$/, '/');
-}
-load(env_home + "jshint.js");
-
-if (typeof(JSHINT) === 'undefined') {
-  print('jshint: Could not load jshint.js, tried "' + env_home + 'jshint.js".');
-  quit();
-}
-
-(function(args){
-    var home  = args[3],
-        name  = args[0],
-        input = args[1],
-        opts  = (function(arg){
-            var opts = {};
-            var item;
-
-            switch (arg) {
-            case undefined:
-            case '':
-                return opts;
-            default:
-                arg = arg.split(',');
-                for (var i = 0, ii = arg.length; i < ii; i++) {
-                    item = arg[i].split(':');
-                    opts[item[0]] = eval(item[1]);
-                }
-                return opts;
-            }
-        })(args[2]);
-
-    if (!name) {
-        print('jshint: No file name was provided.');
-        quit();
-    }
-
-    if (!input) {
-        print('jshint: ' + name + ' contents were not provided to jshint.');
-        quit();
-    }
-
-    if (!JSHINT(input, opts)) {
-        for (var i = 0, err; err = JSHINT.errors[i]; i++) {
-            print(err.reason + ' (line: ' + err.line + ', character: ' + err.character + ')');
-            print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
-            print('');
-        }
-    }
-
-    var data = JSHINT.data();
-    if (data.unused !== undefined) {
-      for (var i = 0, unused; unused = data.unused[i]; i++) {
-        print('Unused variable "' + unused.name + '" (line: ' + unused.line + ')');
-      }
-    }
-
-    // if (data.globals !== undefined) {
-    //   for (var i = 0, globals; global = data.globals[i]; i++) {
-    //     print('Global variable "' + global + '"');
-    //   }
-    // }
-
-    if (data.implieds !== undefined) {
-      for (var i = 0, implied; implied = data.implieds[i]; i++) {
-        print('Implied global variable "' + implied.name + '" (line: ' + implied.line + ')');
-      }
-    }
-
-    // print('Errors: ' + JSHINT.errors.length);
-    // print(JSHINT.report(true));
-
-    quit();
-})(arguments);
diff --git a/jshint/env/jsc.sh b/jshint/env/jsc.sh
deleted file mode 100755 (executable)
index e5baf50..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/sh
-# usage (run from any directory) :
-#   env/jsc.sh /path/to/script.js
-# or with jshint options:
-#   env/jsc.sh /path/to/script.js "{option1:true,option2:false,option3:25}"
-
-alias jsc="/System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc"
-FILE="${1}"
-OPTS="${2}"
-
-FILE_CONTENT=$(cat "${FILE}")
-
-if [ -L $BASH_SOURCE ]; then
-  ENV_HOME="$( cd "$( dirname "$(readlink "$BASH_SOURCE")" )" && pwd )"
-else
-  ENV_HOME="$( cd "$( dirname "$BASH_SOURCE" )" && pwd )"
-fi
-
-LINT_RESULT=$(jsc "${ENV_HOME}"/jsc.js -- "${FILE}" "${FILE_CONTENT}" "${OPTS}" "${ENV_HOME}")
-
-ERRORS=$(echo ${LINT_RESULT} | egrep [^\s] -c)
-
-if [[ ${ERRORS} -ne 0 ]]; then
-       echo "[jshint] Error(s) in ${FILE}:"
-       printf "%s\n" "${LINT_RESULT}"
-else
-       echo "[jshint] ${FILE} passed!"
-fi
-
-exit $((0 + ${ERRORS}))
diff --git a/jshint/env/rhino.js b/jshint/env/rhino.js
deleted file mode 100644 (file)
index f0529cd..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*jshint boss: true, rhino: true */
-/*globals JSHINT*/
-
-(function (args) {
-    var filenames = [],
-        optstr, // arg1=val1,arg2=val2,...
-        predef, // global1=override,global2,global3,...
-        opts   = { rhino: true },
-        retval = 0;
-
-    args.forEach(function (arg) {
-        if (arg.indexOf("=") > -1) {
-            //first time it's the options
-            if (!optstr) {
-                optstr = arg;
-            } else if (!predef) {
-                predef = arg;
-            }
-        } else {
-            filenames.push(arg);
-        }
-    });
-
-    if (filenames.length === 0) {
-        print('Usage: jshint.js file.js');
-        quit(1);
-    }
-
-    if (optstr) {
-        optstr.split(',').forEach(function (arg) {
-            var o = arg.split('=');
-            opts[o[0]] = (function (ov) {
-                switch (ov) {
-                case 'true':
-                    return true;
-                case 'false':
-                    return false;
-                default:
-                    return ov;
-                }
-            }(o[1]));
-        });
-    }
-
-    if (predef) {
-        opts.predef = {};
-        predef.split(',').forEach(function (arg) {
-            var global = arg.split('=');
-            opts.predef[global[0]] = (function (override) {
-                return (override === 'false') ? false : true;
-            }(global[1]));
-        });
-    }
-
-    filenames.forEach(function (name) {
-
-        var input = readFile(name);
-
-        if (!input) {
-            print('jshint: Couldn\'t open file ' + name);
-            quit(1);
-        }
-
-        if (!JSHINT(input, opts)) {
-            for (var i = 0, err; err = JSHINT.errors[i]; i += 1) {
-                print(err.reason + ' (' + name + ':' + err.line + ':' + err.character + ')');
-                print('> ' + (err.evidence || '').replace(/^\s*(\S*(\s+\S+)*)\s*$/, "$1"));
-                print('');
-            }
-            retval = 1;
-        }
-    });
-
-    quit(retval);
-}(arguments));
diff --git a/jshint/env/wsh.js b/jshint/env/wsh.js
deleted file mode 100644 (file)
index d9efb6a..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-/*jshint evil: true, shadow: true, wsh: true */
-/*global JSHINT: false */
-
-(function() {
-       function readFile(path, charset) {
-               try {
-                       var stream = WScript.CreateObject("ADODB.Stream");
-
-                       stream.Charset = charset;
-                       stream.Open();
-                       stream.LoadFromFile(path);
-
-                       var result = stream.ReadText();
-                       stream.close();
-
-                       return result;
-               } catch (ex) {
-                       return null;
-               }
-       }
-
-       var formatters = {
-               errors: function(errors, lines) {
-                       for (var i = 0; i < errors.length; i++) {
-                               var error = errors[i];
-
-                               if (!error) continue;
-
-                               if (i) lines.push("");
-
-                               lines.push("Line " + error.line + " character " + error.character + ": " + error.reason);
-
-                               if (error.evidence) lines.push("    " + error.evidence.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, "$1"));
-                       }
-               },
-
-               implieds: function(implieds, lines) {
-                       lines.push("Implied globals:");
-
-                       var globals = {};
-
-                       for (var i = 0; i < implieds.length; i++) {
-                               var item = implieds[i];
-
-                               if (!(item.name in globals)) globals[item.name] = [];
-
-                               globals[item.name].push(item.line);
-                       }
-
-                       for (var name in globals) {
-                               lines.push("    " + name + ": " + globals[name].join(", "));
-                       }
-               },
-
-               unused: function(unused, lines) {
-                       lines.push("Unused variables:");
-
-                       var func, names = {};
-
-                       for (var i = 0; i < unused.length; i++) {
-                               var item = unused[i];
-
-                               func = item["function"];
-
-                               if (!(func in names)) names[func] = [];
-
-                               names[func].push(item.name + " (" + item.line + ")");
-                       }
-
-                       for (func in names) {
-                               lines.push("    " + func + ": " + names[func].join(", "));
-                       }
-               }
-       };
-
-       var scriptName = WScript.ScriptName;
-       var scriptPath = WScript.ScriptFullName;
-
-       scriptPath = scriptPath.substr(0, scriptPath.length - scriptName.length);
-
-       // load JSHint if the two scripts have not been concatenated
-       if (typeof JSHINT === "undefined") {
-               eval(readFile(scriptPath + "..\\jshint.js", 'utf-8'));
-
-               if (typeof JSHINT === "undefined") {
-                       WScript.StdOut.WriteLine("ERROR: Could not find 'jshint.js'.");
-
-                       WScript.Quit(-2);
-               }
-       }
-
-       var globals = {};
-       var options = {};
-       var named = WScript.Arguments.Named;
-       var unnamed = WScript.Arguments.Unnamed;
-
-       if (unnamed.length !== 1) {
-               WScript.StdOut.WriteLine("    usage: cscript " + scriptName + " [options] <script>");
-               WScript.StdOut.WriteLine("");
-               WScript.StdOut.WriteLine("Scans the specified script with JSHint and reports any errors encountered.  If");
-               WScript.StdOut.WriteLine("the script name is \"-\", it will be read from standard input instead.");
-               WScript.StdOut.WriteLine("");
-               WScript.StdOut.WriteLine("JSHint configuration options can be passed in via optional, Windows-style");
-               WScript.StdOut.WriteLine("arguments.  For example:");
-               WScript.StdOut.WriteLine("    cscript " + scriptName + " /jquery:true myscript.js");
-               WScript.StdOut.WriteLine("    cscript " + scriptName + " /global:QUnit:false,_:false,foo:true foo.js");
-               WScript.StdOut.WriteLine("");
-               WScript.StdOut.WriteLine("By default, we assume that your file is encoded if UTF-8. You can change that");
-               WScript.StdOut.WriteLine("by providing a custom charset option:");
-               WScript.StdOut.WriteLine("    cscript " + scriptName + " /charset:ascii myscript.js");
-
-               WScript.Quit(-1);
-       }
-
-       var script = unnamed(0);
-
-       if (script === "-") {
-               try {
-                       script = WScript.StdIn.ReadAll();
-               } catch (ex) {
-                       script = null;
-               }
-       } else {
-               script = readFile(script, named('charset') || 'utf-8');
-       }
-
-       if (script === null) {
-               WScript.StdOut.WriteLine("ERROR: Could not read target script.");
-
-               WScript.Quit(2);
-       }
-
-       for (var etor = new Enumerator(named); !etor.atEnd(); etor.moveNext()) {
-               var option = etor.item();
-               var value = named(option);
-
-               if (option === "global") {
-                       value = value.split(",");
-
-                       for (var i = 0; i < value.length; i++) {
-                               var name = value[i].split(":");
-
-                               if (name.length === 1 || name[1] === "false") {
-                                       globals[name[0]] = false;
-                               } else if (name[1] === "true") {
-                                       globals[name[0]] = true;
-                               } else {
-                                       WScript.StdOut.WriteLine("Unrecognized value for global: " + name[0]);
-                                       WScript.StdOut.WriteLine("Must be \"true\", \"false\", or omitted.");
-
-                                       WScript.Quit(-1);
-                               }
-                       }
-               } else {
-                       options[option] = value === "true" ? true : value === "false" ? false : value;
-               }
-       }
-
-       JSHINT(script, options, globals);
-
-       var data = JSHINT.data();
-       var lines = [];
-
-       for (var formatter in formatters) {
-               if (data[formatter]) {
-                       if (lines.length) lines.push("");
-
-                       formatters[formatter](data[formatter], lines);
-               }
-       }
-
-       if (lines.length) {
-               for (var i = 0; i < lines.length; i++) {
-                       WScript.StdOut.WriteLine(lines[i]);
-               }
-
-               WScript.Quit(1);
-       } else {
-               WScript.Quit(0);
-       }
-}());
diff --git a/jshint/jshint.js b/jshint/jshint.js
deleted file mode 100644 (file)
index 4be801d..0000000
+++ /dev/null
@@ -1,4314 +0,0 @@
-/*!
- * JSHint, by JSHint Community.
- *
- * Licensed under the same slightly modified MIT license that JSLint is.
- * It stops evil-doers everywhere.
- *
- * JSHint is a derivative work of JSLint:
- *
- *   Copyright (c) 2002 Douglas Crockford  (www.JSLint.com)
- *
- *   Permission is hereby granted, free of charge, to any person obtaining
- *   a copy of this software and associated documentation files (the "Software"),
- *   to deal in the Software without restriction, including without limitation
- *   the rights to use, copy, modify, merge, publish, distribute, sublicense,
- *   and/or sell copies of the Software, and to permit persons to whom
- *   the Software is furnished to do so, subject to the following conditions:
- *
- *   The above copyright notice and this permission notice shall be included
- *   in all copies or substantial portions of the Software.
- *
- *   The Software shall be used for Good, not Evil.
- *
- *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- *   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- *   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
- *   DEALINGS IN THE SOFTWARE.
- *
- * JSHint was forked from 2010-12-16 edition of JSLint.
- *
- */
-
-/*
- JSHINT is a global function. It takes two parameters.
-
-     var myResult = JSHINT(source, option);
-
- The first parameter is either a string or an array of strings. If it is a
- string, it will be split on '\n' or '\r'. If it is an array of strings, it
- is assumed that each string represents one line. The source can be a
- JavaScript text or a JSON text.
-
- The second parameter is an optional object of options which control the
- operation of JSHINT. Most of the options are booleans: They are all
- optional and have a default value of false. One of the options, predef,
- can be an array of names, which will be used to declare global variables,
- or an object whose keys are used as global names, with a boolean value
- that determines if they are assignable.
-
- If it checks out, JSHINT returns true. Otherwise, it returns false.
-
- If false, you can inspect JSHINT.errors to find out the problems.
- JSHINT.errors is an array of objects containing these members:
-
- {
-     line      : The line (relative to 0) at which the lint was found
-     character : The character (relative to 0) at which the lint was found
-     reason    : The problem
-     evidence  : The text line in which the problem occurred
-     raw       : The raw message before the details were inserted
-     a         : The first detail
-     b         : The second detail
-     c         : The third detail
-     d         : The fourth detail
- }
-
- If a fatal error was found, a null will be the last element of the
- JSHINT.errors array.
-
- You can request a Function Report, which shows all of the functions
- and the parameters and vars that they use. This can be used to find
- implied global variables and other problems. The report is in HTML and
- can be inserted in an HTML <body>.
-
-     var myReport = JSHINT.report(limited);
-
- If limited is true, then the report will be limited to only errors.
-
- You can request a data structure which contains JSHint's results.
-
-     var myData = JSHINT.data();
-
- It returns a structure with this form:
-
- {
-     errors: [
-         {
-             line: NUMBER,
-             character: NUMBER,
-             reason: STRING,
-             evidence: STRING
-         }
-     ],
-     functions: [
-         name: STRING,
-         line: NUMBER,
-         last: NUMBER,
-         param: [
-             STRING
-         ],
-         closure: [
-             STRING
-         ],
-         var: [
-             STRING
-         ],
-         exception: [
-             STRING
-         ],
-         outer: [
-             STRING
-         ],
-         unused: [
-             STRING
-         ],
-         global: [
-             STRING
-         ],
-         label: [
-             STRING
-         ]
-     ],
-     globals: [
-         STRING
-     ],
-     member: {
-         STRING: NUMBER
-     },
-     unuseds: [
-         {
-             name: STRING,
-             line: NUMBER
-         }
-     ],
-     implieds: [
-         {
-             name: STRING,
-             line: NUMBER
-         }
-     ],
-     urls: [
-         STRING
-     ],
-     json: BOOLEAN
- }
-
- Empty arrays will not be included.
-
-*/
-
-/*jshint
- evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true,
- undef: true, maxlen: 100, indent:4
-*/
-
-/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", "(begin)",
- "(breakage)", "(context)", "(error)", "(global)", "(identifier)", "(last)",
- "(line)", "(loopage)", "(name)", "(onevar)", "(params)", "(scope)",
- "(statement)", "(verb)", "*", "+", "++", "-", "--", "\/", "<", "<=", "==",
- "===", ">", ">=", $, $$, $A, $F, $H, $R, $break, $continue, $w, Abstract, Ajax,
- __filename, __dirname, ActiveXObject, Array, ArrayBuffer, ArrayBufferView, Audio,
- Autocompleter, Assets, Boolean, Builder, Buffer, Browser, COM, CScript, Canvas,
- CustomAnimation, Class, Control, Chain, Color, Cookie, Core, DataView, Date,
- Debug, Draggable, Draggables, Droppables, Document, DomReady, DOMReady, Drag,
- E, Enumerator, Enumerable, Element, Elements, Error, Effect, EvalError, Event,
- Events, FadeAnimation, Field, Flash, Float32Array, Float64Array, Form,
- FormField, Frame, FormData, Function, Fx, GetObject, Group, Hash, HotKey,
- HTMLElement, HTMLAnchorElement, HTMLBaseElement, HTMLBlockquoteElement,
- HTMLBodyElement, HTMLBRElement, HTMLButtonElement, HTMLCanvasElement, HTMLDirectoryElement,
- HTMLDivElement, HTMLDListElement, HTMLFieldSetElement,
- HTMLFontElement, HTMLFormElement, HTMLFrameElement, HTMLFrameSetElement,
- HTMLHeadElement, HTMLHeadingElement, HTMLHRElement, HTMLHtmlElement,
- HTMLIFrameElement, HTMLImageElement, HTMLInputElement, HTMLIsIndexElement,
- HTMLLabelElement, HTMLLayerElement, HTMLLegendElement, HTMLLIElement,
- HTMLLinkElement, HTMLMapElement, HTMLMenuElement, HTMLMetaElement,
- HTMLModElement, HTMLObjectElement, HTMLOListElement, HTMLOptGroupElement,
- HTMLOptionElement, HTMLParagraphElement, HTMLParamElement, HTMLPreElement,
- HTMLQuoteElement, HTMLScriptElement, HTMLSelectElement, HTMLStyleElement,
- HtmlTable, HTMLTableCaptionElement, HTMLTableCellElement, HTMLTableColElement,
- HTMLTableElement, HTMLTableRowElement, HTMLTableSectionElement,
- HTMLTextAreaElement, HTMLTitleElement, HTMLUListElement, HTMLVideoElement,
- Iframe, IframeShim, Image, Int16Array, Int32Array, Int8Array,
- Insertion, InputValidator, JSON, Keyboard, Locale, LN10, LN2, LOG10E, LOG2E,
- MAX_VALUE, MIN_VALUE, Mask, Math, MenuItem, MoveAnimation, MooTools, Native,
- NEGATIVE_INFINITY, Number, Object, ObjectRange, Option, Options, OverText, PI,
- POSITIVE_INFINITY, PeriodicalExecuter, Point, Position, Prototype, RangeError,
- Rectangle, ReferenceError, RegExp, ResizeAnimation, Request, RotateAnimation,
- SQRT1_2, SQRT2, ScrollBar, ScriptEngine, ScriptEngineBuildVersion,
- ScriptEngineMajorVersion, ScriptEngineMinorVersion, Scriptaculous, Scroller,
- Slick, Slider, Selector, SharedWorker, String, Style, SyntaxError, Sortable, Sortables,
- SortableObserver, Sound, Spinner, System, Swiff, Text, TextArea, Template,
- Timer, Tips, Type, TypeError, Toggle, Try, "use strict", unescape, URI, URIError, URL,
- VBArray, WSH, WScript, XDomainRequest, Web, Window, XMLDOM, XMLHttpRequest, XPathEvaluator,
- XPathException, XPathExpression, XPathNamespace, XPathNSResolver, XPathResult, "\\", a,
- addEventListener, address, alert, apply, applicationCache, arguments, arity,
- asi, b, bitwise, block, blur, boolOptions, boss, browser, c, call, callee,
- caller, cases, charAt, charCodeAt, character, clearInterval, clearTimeout,
- close, closed, closure, comment, condition, confirm, console, constructor,
- content, couch, create, css, curly, d, data, datalist, dd, debug, decodeURI,
- decodeURIComponent, defaultStatus, defineClass, deserialize, devel, document,
- dojo, dijit, dojox, define, edition, else, emit, encodeURI, encodeURIComponent,
- entityify, eqeqeq, eqnull, errors, es5, escape, esnext, eval, event, evidence, evil,
- ex, exception, exec, exps, expr, exports, FileReader, first, floor, focus,
- forin, fragment, frames, from, fromCharCode, fud, funcscope, funct, function, functions,
- g, gc, getComputedStyle, getRow, getter, GLOBAL, global, globals, globalstrict,
- hasOwnProperty, help, history, i, id, identifier, immed, implieds, importPackage, include,
- indent, indexOf, init, ins, instanceOf, isAlpha, isApplicationRunning, isArray,
- isDigit, isFinite, isNaN, iterator, java, join, jshint,
- JSHINT, json, jquery, jQuery, keys, label, labelled, last, lastsemic, laxbreak,
- latedef, lbp, led, left, length, line, load, loadClass, localStorage, location,
- log, loopfunc, m, match, maxerr, maxlen, member,message, meta, module, moveBy,
- moveTo, mootools, multistr, name, navigator, new, newcap, noarg, node, noempty, nomen,
- nonew, nonstandard, nud, onbeforeunload, onblur, onerror, onevar, onecase, onfocus,
- onload, onresize, onunload, open, openDatabase, openURL, opener, opera, options, outer, param,
- parent, parseFloat, parseInt, passfail, plusplus, predef, print, process, prompt,
- proto, prototype, prototypejs, provides, push, quit, range, raw, reach, reason, regexp,
- readFile, readUrl, regexdash, removeEventListener, replace, report, require,
- reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, respond, rhino, right,
- runCommand, scroll, screen, scripturl, scrollBy, scrollTo, scrollbar, search, seal,
- send, serialize, sessionStorage, setInterval, setTimeout, setter, setterToken, shift, slice,
- smarttabs, sort, spawn, split, stack, status, start, strict, sub, substr, supernew, shadow,
- supplant, sum, sync, test, toLowerCase, toString, toUpperCase, toint32, token, top, trailing,
- type, typeOf, Uint16Array, Uint32Array, Uint8Array, undef, undefs, unused, urls, validthis,
- value, valueOf, var, version, WebSocket, white, window, Worker, wsh*/
-
-/*global exports: false */
-
-// We build the application inside a function so that we produce only a single
-// global variable. That function will be invoked immediately, and its return
-// value is the JSHINT function itself.
-
-var JSHINT = (function () {
-    "use strict";
-
-    var anonname,       // The guessed name for anonymous functions.
-
-// These are operators that should not be used with the ! operator.
-
-        bang = {
-            '<'  : true,
-            '<=' : true,
-            '==' : true,
-            '===': true,
-            '!==': true,
-            '!=' : true,
-            '>'  : true,
-            '>=' : true,
-            '+'  : true,
-            '-'  : true,
-            '*'  : true,
-            '/'  : true,
-            '%'  : true
-        },
-
-        // These are the JSHint boolean options.
-        boolOptions = {
-            asi         : true, // if automatic semicolon insertion should be tolerated
-            bitwise     : true, // if bitwise operators should not be allowed
-            boss        : true, // if advanced usage of assignments should be allowed
-            browser     : true, // if the standard browser globals should be predefined
-            couch       : true, // if CouchDB globals should be predefined
-            curly       : true, // if curly braces around all blocks should be required
-            debug       : true, // if debugger statements should be allowed
-            devel       : true, // if logging globals should be predefined (console,
-                                // alert, etc.)
-            dojo        : true, // if Dojo Toolkit globals should be predefined
-            eqeqeq      : true, // if === should be required
-            eqnull      : true, // if == null comparisons should be tolerated
-            es5         : true, // if ES5 syntax should be allowed
-            esnext      : true, // if es.next specific syntax should be allowed
-            evil        : true, // if eval should be allowed
-            expr        : true, // if ExpressionStatement should be allowed as Programs
-            forin       : true, // if for in statements must filter
-            funcscope   : true, // if only function scope should be used for scope tests
-            globalstrict: true, // if global "use strict"; should be allowed (also
-                                // enables 'strict')
-            immed       : true, // if immediate invocations must be wrapped in parens
-            iterator    : true, // if the `__iterator__` property should be allowed
-            jquery      : true, // if jQuery globals should be predefined
-            lastsemic   : true, // if semicolons may be ommitted for the trailing
-                                // statements inside of a one-line blocks.
-            latedef     : true, // if the use before definition should not be tolerated
-            laxbreak    : true, // if line breaks should not be checked
-            loopfunc    : true, // if functions should be allowed to be defined within
-                                // loops
-            mootools    : true, // if MooTools globals should be predefined
-            multistr    : true, // allow multiline strings
-            newcap      : true, // if constructor names must be capitalized
-            noarg       : true, // if arguments.caller and arguments.callee should be
-                                // disallowed
-            node        : true, // if the Node.js environment globals should be
-                                // predefined
-            noempty     : true, // if empty blocks should be disallowed
-            nonew       : true, // if using `new` for side-effects should be disallowed
-            nonstandard : true, // if non-standard (but widely adopted) globals should
-                                // be predefined
-            nomen       : true, // if names should be checked
-            onevar      : true, // if only one var statement per function should be
-                                // allowed
-            onecase     : true, // if one case switch statements should be allowed
-            passfail    : true, // if the scan should stop on first error
-            plusplus    : true, // if increment/decrement should not be allowed
-            proto       : true, // if the `__proto__` property should be allowed
-            prototypejs : true, // if Prototype and Scriptaculous globals should be
-                                // predefined
-            regexdash   : true, // if unescaped first/last dash (-) inside brackets
-                                // should be tolerated
-            regexp      : true, // if the . should not be allowed in regexp literals
-            rhino       : true, // if the Rhino environment globals should be predefined
-            undef       : true, // if variables should be declared before used
-            scripturl   : true, // if script-targeted URLs should be tolerated
-            shadow      : true, // if variable shadowing should be tolerated
-            smarttabs   : true, // if smarttabs should be tolerated
-                                // (http://www.emacswiki.org/emacs/SmartTabs)
-            strict      : true, // require the "use strict"; pragma
-            sub         : true, // if all forms of subscript notation are tolerated
-            supernew    : true, // if `new function () { ... };` and `new Object;`
-                                // should be tolerated
-            trailing    : true, // if trailing whitespace rules apply
-            validthis   : true, // if 'this' inside a non-constructor function is valid.
-                                // This is a function scoped option only.
-            white       : true, // if strict whitespace rules apply
-            wsh         : true  // if the Windows Scripting Host environment globals
-                                // should be predefined
-        },
-
-        // browser contains a set of global names which are commonly provided by a
-        // web browser environment.
-        browser = {
-            ArrayBuffer              :  false,
-            ArrayBufferView          :  false,
-            Audio                    :  false,
-            addEventListener         :  false,
-            applicationCache         :  false,
-            blur                     :  false,
-            clearInterval            :  false,
-            clearTimeout             :  false,
-            close                    :  false,
-            closed                   :  false,
-            DataView                 :  false,
-            defaultStatus            :  false,
-            document                 :  false,
-            event                    :  false,
-            FileReader               :  false,
-            Float32Array             :  false,
-            Float64Array             :  false,
-            FormData                 :  false,
-            focus                    :  false,
-            frames                   :  false,
-            getComputedStyle         :  false,
-            HTMLElement              :  false,
-            HTMLAnchorElement        :  false,
-            HTMLBaseElement          :  false,
-            HTMLBlockquoteElement    :  false,
-            HTMLBodyElement          :  false,
-            HTMLBRElement            :  false,
-            HTMLButtonElement        :  false,
-            HTMLCanvasElement        :  false,
-            HTMLDirectoryElement     :  false,
-            HTMLDivElement           :  false,
-            HTMLDListElement         :  false,
-            HTMLFieldSetElement      :  false,
-            HTMLFontElement          :  false,
-            HTMLFormElement          :  false,
-            HTMLFrameElement         :  false,
-            HTMLFrameSetElement      :  false,
-            HTMLHeadElement          :  false,
-            HTMLHeadingElement       :  false,
-            HTMLHRElement            :  false,
-            HTMLHtmlElement          :  false,
-            HTMLIFrameElement        :  false,
-            HTMLImageElement         :  false,
-            HTMLInputElement         :  false,
-            HTMLIsIndexElement       :  false,
-            HTMLLabelElement         :  false,
-            HTMLLayerElement         :  false,
-            HTMLLegendElement        :  false,
-            HTMLLIElement            :  false,
-            HTMLLinkElement          :  false,
-            HTMLMapElement           :  false,
-            HTMLMenuElement          :  false,
-            HTMLMetaElement          :  false,
-            HTMLModElement           :  false,
-            HTMLObjectElement        :  false,
-            HTMLOListElement         :  false,
-            HTMLOptGroupElement      :  false,
-            HTMLOptionElement        :  false,
-            HTMLParagraphElement     :  false,
-            HTMLParamElement         :  false,
-            HTMLPreElement           :  false,
-            HTMLQuoteElement         :  false,
-            HTMLScriptElement        :  false,
-            HTMLSelectElement        :  false,
-            HTMLStyleElement         :  false,
-            HTMLTableCaptionElement  :  false,
-            HTMLTableCellElement     :  false,
-            HTMLTableColElement      :  false,
-            HTMLTableElement         :  false,
-            HTMLTableRowElement      :  false,
-            HTMLTableSectionElement  :  false,
-            HTMLTextAreaElement      :  false,
-            HTMLTitleElement         :  false,
-            HTMLUListElement         :  false,
-            HTMLVideoElement         :  false,
-            history                  :  false,
-            Int16Array               :  false,
-            Int32Array               :  false,
-            Int8Array                :  false,
-            Image                    :  false,
-            length                   :  false,
-            localStorage             :  false,
-            location                 :  false,
-            moveBy                   :  false,
-            moveTo                   :  false,
-            name                     :  false,
-            navigator                :  false,
-            onbeforeunload           :  true,
-            onblur                   :  true,
-            onerror                  :  true,
-            onfocus                  :  true,
-            onload                   :  true,
-            onresize                 :  true,
-            onunload                 :  true,
-            open                     :  false,
-            openDatabase             :  false,
-            opener                   :  false,
-            Option                   :  false,
-            parent                   :  false,
-            print                    :  false,
-            removeEventListener      :  false,
-            resizeBy                 :  false,
-            resizeTo                 :  false,
-            screen                   :  false,
-            scroll                   :  false,
-            scrollBy                 :  false,
-            scrollTo                 :  false,
-            sessionStorage           :  false,
-            setInterval              :  false,
-            setTimeout               :  false,
-            SharedWorker             :  false,
-            status                   :  false,
-            top                      :  false,
-            Uint16Array              :  false,
-            Uint32Array              :  false,
-            Uint8Array               :  false,
-            WebSocket                :  false,
-            window                   :  false,
-            Worker                   :  false,
-            XMLHttpRequest           :  false,
-            XPathEvaluator           :  false,
-            XPathException           :  false,
-            XPathExpression          :  false,
-            XPathNamespace           :  false,
-            XPathNSResolver          :  false,
-            XPathResult              :  false
-        },
-
-        couch = {
-            "require" : false,
-            respond   : false,
-            getRow    : false,
-            emit      : false,
-            send      : false,
-            start     : false,
-            sum       : false,
-            log       : false,
-            exports   : false,
-            module    : false,
-            provides  : false
-        },
-
-        devel = {
-            alert   : false,
-            confirm : false,
-            console : false,
-            Debug   : false,
-            opera   : false,
-            prompt  : false
-        },
-
-        dojo = {
-            dojo      : false,
-            dijit     : false,
-            dojox     : false,
-            define    : false,
-            "require" : false
-        },
-
-        escapes = {
-            '\b': '\\b',
-            '\t': '\\t',
-            '\n': '\\n',
-            '\f': '\\f',
-            '\r': '\\r',
-            '"' : '\\"',
-            '/' : '\\/',
-            '\\': '\\\\'
-        },
-
-        funct,          // The current function
-
-        functionicity = [
-            'closure', 'exception', 'global', 'label',
-            'outer', 'unused', 'var'
-        ],
-
-        functions,      // All of the functions
-
-        global,         // The global scope
-        implied,        // Implied globals
-        inblock,
-        indent,
-        jsonmode,
-
-        jquery = {
-            '$'    : false,
-            jQuery : false
-        },
-
-        lines,
-        lookahead,
-        member,
-        membersOnly,
-
-        mootools = {
-            '$'             : false,
-            '$$'            : false,
-            Assets          : false,
-            Browser         : false,
-            Chain           : false,
-            Class           : false,
-            Color           : false,
-            Cookie          : false,
-            Core            : false,
-            Document        : false,
-            DomReady        : false,
-            DOMReady        : false,
-            Drag            : false,
-            Element         : false,
-            Elements        : false,
-            Event           : false,
-            Events          : false,
-            Fx              : false,
-            Group           : false,
-            Hash            : false,
-            HtmlTable       : false,
-            Iframe          : false,
-            IframeShim      : false,
-            InputValidator  : false,
-            instanceOf      : false,
-            Keyboard        : false,
-            Locale          : false,
-            Mask            : false,
-            MooTools        : false,
-            Native          : false,
-            Options         : false,
-            OverText        : false,
-            Request         : false,
-            Scroller        : false,
-            Slick           : false,
-            Slider          : false,
-            Sortables       : false,
-            Spinner         : false,
-            Swiff           : false,
-            Tips            : false,
-            Type            : false,
-            typeOf          : false,
-            URI             : false,
-            Window          : false
-        },
-
-        nexttoken,
-
-        node = {
-            __filename    : false,
-            __dirname     : false,
-            Buffer        : false,
-            console       : false,
-            exports       : false,
-            GLOBAL        : false,
-            global        : false,
-            module        : false,
-            process       : false,
-            require       : false,
-            setTimeout    : false,
-            clearTimeout  : false,
-            setInterval   : false,
-            clearInterval : false
-        },
-
-        noreach,
-        option,
-        predefined,     // Global variables defined by option
-        prereg,
-        prevtoken,
-
-        prototypejs = {
-            '$'               : false,
-            '$$'              : false,
-            '$A'              : false,
-            '$F'              : false,
-            '$H'              : false,
-            '$R'              : false,
-            '$break'          : false,
-            '$continue'       : false,
-            '$w'              : false,
-            Abstract          : false,
-            Ajax              : false,
-            Class             : false,
-            Enumerable        : false,
-            Element           : false,
-            Event             : false,
-            Field             : false,
-            Form              : false,
-            Hash              : false,
-            Insertion         : false,
-            ObjectRange       : false,
-            PeriodicalExecuter: false,
-            Position          : false,
-            Prototype         : false,
-            Selector          : false,
-            Template          : false,
-            Toggle            : false,
-            Try               : false,
-            Autocompleter     : false,
-            Builder           : false,
-            Control           : false,
-            Draggable         : false,
-            Draggables        : false,
-            Droppables        : false,
-            Effect            : false,
-            Sortable          : false,
-            SortableObserver  : false,
-            Sound             : false,
-            Scriptaculous     : false
-        },
-
-        rhino = {
-            defineClass  : false,
-            deserialize  : false,
-            gc           : false,
-            help         : false,
-            importPackage: false,
-            "java"       : false,
-            load         : false,
-            loadClass    : false,
-            print        : false,
-            quit         : false,
-            readFile     : false,
-            readUrl      : false,
-            runCommand   : false,
-            seal         : false,
-            serialize    : false,
-            spawn        : false,
-            sync         : false,
-            toint32      : false,
-            version      : false
-        },
-
-        scope,      // The current scope
-        stack,
-
-        // standard contains the global names that are provided by the
-        // ECMAScript standard.
-        standard = {
-            Array               : false,
-            Boolean             : false,
-            Date                : false,
-            decodeURI           : false,
-            decodeURIComponent  : false,
-            encodeURI           : false,
-            encodeURIComponent  : false,
-            Error               : false,
-            'eval'              : false,
-            EvalError           : false,
-            Function            : false,
-            hasOwnProperty      : false,
-            isFinite            : false,
-            isNaN               : false,
-            JSON                : false,
-            Math                : false,
-            Number              : false,
-            Object              : false,
-            parseInt            : false,
-            parseFloat          : false,
-            RangeError          : false,
-            ReferenceError      : false,
-            RegExp              : false,
-            String              : false,
-            SyntaxError         : false,
-            TypeError           : false,
-            URIError            : false
-        },
-
-        // widely adopted global names that are not part of ECMAScript standard
-        nonstandard = {
-            escape              : false,
-            unescape            : false
-        },
-
-        standard_member = {
-            E                   : true,
-            LN2                 : true,
-            LN10                : true,
-            LOG2E               : true,
-            LOG10E              : true,
-            MAX_VALUE           : true,
-            MIN_VALUE           : true,
-            NEGATIVE_INFINITY   : true,
-            PI                  : true,
-            POSITIVE_INFINITY   : true,
-            SQRT1_2             : true,
-            SQRT2               : true
-        },
-
-        directive,
-        syntax = {},
-        tab,
-        token,
-        urls,
-        useESNextSyntax,
-        warnings,
-
-        wsh = {
-            ActiveXObject             : true,
-            Enumerator                : true,
-            GetObject                 : true,
-            ScriptEngine              : true,
-            ScriptEngineBuildVersion  : true,
-            ScriptEngineMajorVersion  : true,
-            ScriptEngineMinorVersion  : true,
-            VBArray                   : true,
-            WSH                       : true,
-            WScript                   : true,
-            XDomainRequest            : true
-        };
-
-    // Regular expressions. Some of these are stupidly long.
-    var ax, cx, tx, nx, nxg, lx, ix, jx, ft;
-    (function () {
-        /*jshint maxlen:300 */
-
-        // unsafe comment or string
-        ax = /@cc|<\/?|script|\]\s*\]|<\s*!|&lt/i;
-
-        // unsafe characters that are silently deleted by one or more browsers
-        cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
-
-        // token
-        tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/;
-
-        // characters in strings that need escapement
-        nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/;
-        nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
-
-        // star slash
-        lx = /\*\/|\/\*/;
-
-        // identifier
-        ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/;
-
-        // javascript url
-        jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;
-
-        // catches /* falls through */ comments
-        ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/;
-    }());
-
-    function F() {}     // Used by Object.create
-
-    function is_own(object, name) {
-
-// The object.hasOwnProperty method fails when the property under consideration
-// is named 'hasOwnProperty'. So we have to use this more convoluted form.
-
-        return Object.prototype.hasOwnProperty.call(object, name);
-    }
-
-// Provide critical ES5 functions to ES3.
-
-    if (typeof Array.isArray !== 'function') {
-        Array.isArray = function (o) {
-            return Object.prototype.toString.apply(o) === '[object Array]';
-        };
-    }
-
-    if (typeof Object.create !== 'function') {
-        Object.create = function (o) {
-            F.prototype = o;
-            return new F();
-        };
-    }
-
-    if (typeof Object.keys !== 'function') {
-        Object.keys = function (o) {
-            var a = [], k;
-            for (k in o) {
-                if (is_own(o, k)) {
-                    a.push(k);
-                }
-            }
-            return a;
-        };
-    }
-
-// Non standard methods
-
-    if (typeof String.prototype.entityify !== 'function') {
-        String.prototype.entityify = function () {
-            return this
-                .replace(/&/g, '&amp;')
-                .replace(/</g, '&lt;')
-                .replace(/>/g, '&gt;');
-        };
-    }
-
-    if (typeof String.prototype.isAlpha !== 'function') {
-        String.prototype.isAlpha = function () {
-            return (this >= 'a' && this <= 'z\uffff') ||
-                (this >= 'A' && this <= 'Z\uffff');
-        };
-    }
-
-    if (typeof String.prototype.isDigit !== 'function') {
-        String.prototype.isDigit = function () {
-            return (this >= '0' && this <= '9');
-        };
-    }
-
-    if (typeof String.prototype.supplant !== 'function') {
-        String.prototype.supplant = function (o) {
-            return this.replace(/\{([^{}]*)\}/g, function (a, b) {
-                var r = o[b];
-                return typeof r === 'string' || typeof r === 'number' ? r : a;
-            });
-        };
-    }
-
-    if (typeof String.prototype.name !== 'function') {
-        String.prototype.name = function () {
-
-// If the string looks like an identifier, then we can return it as is.
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can simply slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe
-// sequences.
-
-            if (ix.test(this)) {
-                return this;
-            }
-            if (nx.test(this)) {
-                return '"' + this.replace(nxg, function (a) {
-                    var c = escapes[a];
-                    if (c) {
-                        return c;
-                    }
-                    return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4);
-                }) + '"';
-            }
-            return '"' + this + '"';
-        };
-    }
-
-
-    function combine(t, o) {
-        var n;
-        for (n in o) {
-            if (is_own(o, n)) {
-                t[n] = o[n];
-            }
-        }
-    }
-
-    function assume() {
-        if (option.couch) {
-            combine(predefined, couch);
-        }
-
-        if (option.rhino) {
-            combine(predefined, rhino);
-        }
-
-        if (option.prototypejs) {
-            combine(predefined, prototypejs);
-        }
-
-        if (option.node) {
-            combine(predefined, node);
-        }
-
-        if (option.devel) {
-            combine(predefined, devel);
-        }
-
-        if (option.dojo) {
-            combine(predefined, dojo);
-        }
-
-        if (option.browser) {
-            combine(predefined, browser);
-        }
-
-        if (option.nonstandard) {
-            combine(predefined, nonstandard);
-        }
-
-        if (option.jquery) {
-            combine(predefined, jquery);
-        }
-
-        if (option.mootools) {
-            combine(predefined, mootools);
-        }
-
-        if (option.wsh) {
-            combine(predefined, wsh);
-        }
-
-        if (option.esnext) {
-            useESNextSyntax();
-        }
-
-        if (option.globalstrict && option.strict !== false) {
-            option.strict = true;
-        }
-    }
-
-
-    // Produce an error warning.
-    function quit(message, line, chr) {
-        var percentage = Math.floor((line / lines.length) * 100);
-
-        throw {
-            name: 'JSHintError',
-            line: line,
-            character: chr,
-            message: message + " (" + percentage + "% scanned).",
-            raw: message
-        };
-    }
-
-    function isundef(scope, m, t, a) {
-        return JSHINT.undefs.push([scope, m, t, a]);
-    }
-
-    function warning(m, t, a, b, c, d) {
-        var ch, l, w;
-        t = t || nexttoken;
-        if (t.id === '(end)') {  // `~
-            t = token;
-        }
-        l = t.line || 0;
-        ch = t.from || 0;
-        w = {
-            id: '(error)',
-            raw: m,
-            evidence: lines[l - 1] || '',
-            line: l,
-            character: ch,
-            a: a,
-            b: b,
-            c: c,
-            d: d
-        };
-        w.reason = m.supplant(w);
-        JSHINT.errors.push(w);
-        if (option.passfail) {
-            quit('Stopping. ', l, ch);
-        }
-        warnings += 1;
-        if (warnings >= option.maxerr) {
-            quit("Too many errors.", l, ch);
-        }
-        return w;
-    }
-
-    function warningAt(m, l, ch, a, b, c, d) {
-        return warning(m, {
-            line: l,
-            from: ch
-        }, a, b, c, d);
-    }
-
-    function error(m, t, a, b, c, d) {
-        var w = warning(m, t, a, b, c, d);
-    }
-
-    function errorAt(m, l, ch, a, b, c, d) {
-        return error(m, {
-            line: l,
-            from: ch
-        }, a, b, c, d);
-    }
-
-
-
-// lexical analysis and token construction
-
-    var lex = (function lex() {
-        var character, from, line, s;
-
-// Private lex methods
-
-        function nextLine() {
-            var at,
-                tw; // trailing whitespace check
-
-            if (line >= lines.length)
-                return false;
-
-            character = 1;
-            s = lines[line];
-            line += 1;
-
-            // If smarttabs option is used check for spaces followed by tabs only.
-            // Otherwise check for any occurence of mixed tabs and spaces.
-            if (option.smarttabs)
-                at = s.search(/ \t/);
-            else
-                at = s.search(/ \t|\t /);
-
-            if (at >= 0)
-                warningAt("Mixed spaces and tabs.", line, at + 1);
-
-            s = s.replace(/\t/g, tab);
-            at = s.search(cx);
-
-            if (at >= 0)
-                warningAt("Unsafe character.", line, at);
-
-            if (option.maxlen && option.maxlen < s.length)
-                warningAt("Line too long.", line, s.length);
-
-            // Check for trailing whitespaces
-            tw = /\s+$/.test(s);
-            if (option.trailing && tw && !/^\s+$/.test(s)) {
-                warningAt("Trailing whitespace.", line, tw);
-            }
-            return true;
-        }
-
-// Produce a token object.  The token inherits from a syntax symbol.
-
-        function it(type, value) {
-            var i, t;
-            if (type === '(color)' || type === '(range)') {
-                t = {type: type};
-            } else if (type === '(punctuator)' ||
-                    (type === '(identifier)' && is_own(syntax, value))) {
-                t = syntax[value] || syntax['(error)'];
-            } else {
-                t = syntax[type];
-            }
-            t = Object.create(t);
-            if (type === '(string)' || type === '(range)') {
-                if (!option.scripturl && jx.test(value)) {
-                    warningAt("Script URL.", line, from);
-                }
-            }
-            if (type === '(identifier)') {
-                t.identifier = true;
-                if (value === '__proto__' && !option.proto) {
-                    warningAt("The '{a}' property is deprecated.",
-                        line, from, value);
-                } else if (value === '__iterator__' && !option.iterator) {
-                    warningAt("'{a}' is only available in JavaScript 1.7.",
-                        line, from, value);
-                } else if (option.nomen && (value.charAt(0) === '_' ||
-                         value.charAt(value.length - 1) === '_')) {
-                    if (!option.node || token.id === '.' ||
-                            (value !== '__dirname' && value !== '__filename')) {
-                        warningAt("Unexpected {a} in '{b}'.", line, from, "dangling '_'", value);
-                    }
-                }
-            }
-            t.value = value;
-            t.line = line;
-            t.character = character;
-            t.from = from;
-            i = t.id;
-            if (i !== '(endline)') {
-                prereg = i &&
-                    (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) ||
-                    i === 'return' ||
-                    i === 'case');
-            }
-            return t;
-        }
-
-        // Public lex methods
-        return {
-            init: function (source) {
-                if (typeof source === 'string') {
-                    lines = source
-                        .replace(/\r\n/g, '\n')
-                        .replace(/\r/g, '\n')
-                        .split('\n');
-                } else {
-                    lines = source;
-                }
-
-                // If the first line is a shebang (#!), make it a blank and move on.
-                // Shebangs are used by Node scripts.
-                if (lines[0] && lines[0].substr(0, 2) === '#!')
-                    lines[0] = '';
-
-                line = 0;
-                nextLine();
-                from = 1;
-            },
-
-            range: function (begin, end) {
-                var c, value = '';
-                from = character;
-                if (s.charAt(0) !== begin) {
-                    errorAt("Expected '{a}' and instead saw '{b}'.",
-                            line, character, begin, s.charAt(0));
-                }
-                for (;;) {
-                    s = s.slice(1);
-                    character += 1;
-                    c = s.charAt(0);
-                    switch (c) {
-                    case '':
-                        errorAt("Missing '{a}'.", line, character, c);
-                        break;
-                    case end:
-                        s = s.slice(1);
-                        character += 1;
-                        return it('(range)', value);
-                    case '\\':
-                        warningAt("Unexpected '{a}'.", line, character, c);
-                    }
-                    value += c;
-                }
-
-            },
-
-
-            // token -- this is called by advance to get the next token
-            token: function () {
-                var b, c, captures, d, depth, high, i, l, low, q, t, isLiteral, isInRange;
-
-                function match(x) {
-                    var r = x.exec(s), r1;
-                    if (r) {
-                        l = r[0].length;
-                        r1 = r[1];
-                        c = r1.charAt(0);
-                        s = s.substr(l);
-                        from = character + l - r1.length;
-                        character += l;
-                        return r1;
-                    }
-                }
-
-                function string(x) {
-                    var c, j, r = '', allowNewLine = false;
-
-                    if (jsonmode && x !== '"') {
-                        warningAt("Strings must use doublequote.",
-                                line, character);
-                    }
-
-                    function esc(n) {
-                        var i = parseInt(s.substr(j + 1, n), 16);
-                        j += n;
-                        if (i >= 32 && i <= 126 &&
-                                i !== 34 && i !== 92 && i !== 39) {
-                            warningAt("Unnecessary escapement.", line, character);
-                        }
-                        character += n;
-                        c = String.fromCharCode(i);
-                    }
-                    j = 0;
-unclosedString:     for (;;) {
-                        while (j >= s.length) {
-                            j = 0;
-
-                            var cl = line, cf = from;
-                            if (!nextLine()) {
-                                errorAt("Unclosed string.", cl, cf);
-                                break unclosedString;
-                            }
-
-                            if (allowNewLine) {
-                                allowNewLine = false;
-                            } else {
-                                warningAt("Unclosed string.", cl, cf);
-                            }
-                        }
-                        c = s.charAt(j);
-                        if (c === x) {
-                            character += 1;
-                            s = s.substr(j + 1);
-                            return it('(string)', r, x);
-                        }
-                        if (c < ' ') {
-                            if (c === '\n' || c === '\r') {
-                                break;
-                            }
-                            warningAt("Control character in string: {a}.",
-                                    line, character + j, s.slice(0, j));
-                        } else if (c === '\\') {
-                            j += 1;
-                            character += 1;
-                            c = s.charAt(j);
-                            switch (c) {
-                            case '\\':
-                            case '"':
-                            case '/':
-                                break;
-                            case '\'':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\'.", line, character);
-                                }
-                                break;
-                            case 'b':
-                                c = '\b';
-                                break;
-                            case 'f':
-                                c = '\f';
-                                break;
-                            case 'n':
-                                c = '\n';
-                                break;
-                            case 'r':
-                                c = '\r';
-                                break;
-                            case 't':
-                                c = '\t';
-                                break;
-                            case 'u':
-                                esc(4);
-                                break;
-                            case 'v':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\v.", line, character);
-                                }
-                                c = '\v';
-                                break;
-                            case 'x':
-                                if (jsonmode) {
-                                    warningAt("Avoid \\x-.", line, character);
-                                }
-                                esc(2);
-                                break;
-                            case '':
-                                // last character is escape character
-                                // always allow new line if escaped, but show
-                                // warning if option is not set
-                                allowNewLine = true;
-                                if (option.multistr) {
-                                    if (jsonmode) {
-                                        warningAt("Avoid EOL escapement.", line, character);
-                                    }
-                                    c = '';
-                                    character -= 1;
-                                    break;
-                                }
-                                warningAt("Bad escapement of EOL. Use option multistr if needed.",
-                                    line, character);
-                                break;
-                            default:
-                                warningAt("Bad escapement.", line, character);
-                            }
-                        }
-                        r += c;
-                        character += 1;
-                        j += 1;
-                    }
-                }
-
-                for (;;) {
-                    if (!s) {
-                        return it(nextLine() ? '(endline)' : '(end)', '');
-                    }
-                    t = match(tx);
-                    if (!t) {
-                        t = '';
-                        c = '';
-                        while (s && s < '!') {
-                            s = s.substr(1);
-                        }
-                        if (s) {
-                            errorAt("Unexpected '{a}'.", line, character, s.substr(0, 1));
-                            s = '';
-                        }
-                    } else {
-
-    //      identifier
-
-                        if (c.isAlpha() || c === '_' || c === '$') {
-                            return it('(identifier)', t);
-                        }
-
-    //      number
-
-                        if (c.isDigit()) {
-                            if (!isFinite(Number(t))) {
-                                warningAt("Bad number '{a}'.",
-                                    line, character, t);
-                            }
-                            if (s.substr(0, 1).isAlpha()) {
-                                warningAt("Missing space after '{a}'.",
-                                        line, character, t);
-                            }
-                            if (c === '0') {
-                                d = t.substr(1, 1);
-                                if (d.isDigit()) {
-                                    if (token.id !== '.') {
-                                        warningAt("Don't use extra leading zeros '{a}'.",
-                                            line, character, t);
-                                    }
-                                } else if (jsonmode && (d === 'x' || d === 'X')) {
-                                    warningAt("Avoid 0x-. '{a}'.",
-                                            line, character, t);
-                                }
-                            }
-                            if (t.substr(t.length - 1) === '.') {
-                                warningAt(
-"A trailing decimal point can be confused with a dot '{a}'.", line, character, t);
-                            }
-                            return it('(number)', t);
-                        }
-                        switch (t) {
-
-    //      string
-
-                        case '"':
-                        case "'":
-                            return string(t);
-
-    //      // comment
-
-                        case '//':
-                            s = '';
-                            token.comment = true;
-                            break;
-
-    //      /* comment
-
-                        case '/*':
-                            for (;;) {
-                                i = s.search(lx);
-                                if (i >= 0) {
-                                    break;
-                                }
-                                if (!nextLine()) {
-                                    errorAt("Unclosed comment.", line, character);
-                                }
-                            }
-                            character += i + 2;
-                            if (s.substr(i, 1) === '/') {
-                                errorAt("Nested comment.", line, character);
-                            }
-                            s = s.substr(i + 2);
-                            token.comment = true;
-                            break;
-
-    //      /*members /*jshint /*global
-
-                        case '/*members':
-                        case '/*member':
-                        case '/*jshint':
-                        case '/*jslint':
-                        case '/*global':
-                        case '*/':
-                            return {
-                                value: t,
-                                type: 'special',
-                                line: line,
-                                character: character,
-                                from: from
-                            };
-
-                        case '':
-                            break;
-    //      /
-                        case '/':
-                            if (token.id === '/=') {
-                                errorAt("A regular expression literal can be confused with '/='.",
-                                    line, from);
-                            }
-                            if (prereg) {
-                                depth = 0;
-                                captures = 0;
-                                l = 0;
-                                for (;;) {
-                                    b = true;
-                                    c = s.charAt(l);
-                                    l += 1;
-                                    switch (c) {
-                                    case '':
-                                        errorAt("Unclosed regular expression.", line, from);
-                                        return quit('Stopping.', line, from);
-                                    case '/':
-                                        if (depth > 0) {
-                                            warningAt("{a} unterminated regular expression " +
-                                                "group(s).", line, from + l, depth);
-                                        }
-                                        c = s.substr(0, l - 1);
-                                        q = {
-                                            g: true,
-                                            i: true,
-                                            m: true
-                                        };
-                                        while (q[s.charAt(l)] === true) {
-                                            q[s.charAt(l)] = false;
-                                            l += 1;
-                                        }
-                                        character += l;
-                                        s = s.substr(l);
-                                        q = s.charAt(0);
-                                        if (q === '/' || q === '*') {
-                                            errorAt("Confusing regular expression.",
-                                                    line, from);
-                                        }
-                                        return it('(regexp)', c);
-                                    case '\\':
-                                        c = s.charAt(l);
-                                        if (c < ' ') {
-                                            warningAt(
-"Unexpected control character in regular expression.", line, from + l);
-                                        } else if (c === '<') {
-                                            warningAt(
-"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
-                                        }
-                                        l += 1;
-                                        break;
-                                    case '(':
-                                        depth += 1;
-                                        b = false;
-                                        if (s.charAt(l) === '?') {
-                                            l += 1;
-                                            switch (s.charAt(l)) {
-                                            case ':':
-                                            case '=':
-                                            case '!':
-                                                l += 1;
-                                                break;
-                                            default:
-                                                warningAt(
-"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l));
-                                            }
-                                        } else {
-                                            captures += 1;
-                                        }
-                                        break;
-                                    case '|':
-                                        b = false;
-                                        break;
-                                    case ')':
-                                        if (depth === 0) {
-                                            warningAt("Unescaped '{a}'.",
-                                                    line, from + l, ')');
-                                        } else {
-                                            depth -= 1;
-                                        }
-                                        break;
-                                    case ' ':
-                                        q = 1;
-                                        while (s.charAt(l) === ' ') {
-                                            l += 1;
-                                            q += 1;
-                                        }
-                                        if (q > 1) {
-                                            warningAt(
-"Spaces are hard to count. Use {{a}}.", line, from + l, q);
-                                        }
-                                        break;
-                                    case '[':
-                                        c = s.charAt(l);
-                                        if (c === '^') {
-                                            l += 1;
-                                            if (option.regexp) {
-                                                warningAt("Insecure '{a}'.",
-                                                        line, from + l, c);
-                                            } else if (s.charAt(l) === ']') {
-                                                errorAt("Unescaped '{a}'.",
-                                                    line, from + l, '^');
-                                            }
-                                        }
-                                        if (c === ']') {
-                                            warningAt("Empty class.", line,
-                                                    from + l - 1);
-                                        }
-                                        isLiteral = false;
-                                        isInRange = false;
-klass:                                  do {
-                                            c = s.charAt(l);
-                                            l += 1;
-                                            switch (c) {
-                                            case '[':
-                                            case '^':
-                                                warningAt("Unescaped '{a}'.",
-                                                        line, from + l, c);
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '-':
-                                                if (isLiteral && !isInRange) {
-                                                    isLiteral = false;
-                                                    isInRange = true;
-                                                } else if (isInRange) {
-                                                    isInRange = false;
-                                                } else if (s.charAt(l) === ']') {
-                                                    isInRange = true;
-                                                } else {
-                                                    if (option.regexdash !== (l === 2 || (l === 3 &&
-                                                        s.charAt(1) === '^'))) {
-                                                        warningAt("Unescaped '{a}'.",
-                                                            line, from + l - 1, '-');
-                                                    }
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case ']':
-                                                if (isInRange && !option.regexdash) {
-                                                    warningAt("Unescaped '{a}'.",
-                                                            line, from + l - 1, '-');
-                                                }
-                                                break klass;
-                                            case '\\':
-                                                c = s.charAt(l);
-                                                if (c < ' ') {
-                                                    warningAt(
-"Unexpected control character in regular expression.", line, from + l);
-                                                } else if (c === '<') {
-                                                    warningAt(
-"Unexpected escaped character '{a}' in regular expression.", line, from + l, c);
-                                                }
-                                                l += 1;
-
-                                                // \w, \s and \d are never part of a character range
-                                                if (/[wsd]/i.test(c)) {
-                                                    if (isInRange) {
-                                                        warningAt("Unescaped '{a}'.",
-                                                            line, from + l, '-');
-                                                        isInRange = false;
-                                                    }
-                                                    isLiteral = false;
-                                                } else if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '/':
-                                                warningAt("Unescaped '{a}'.",
-                                                        line, from + l - 1, '/');
-
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            case '<':
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                                break;
-                                            default:
-                                                if (isInRange) {
-                                                    isInRange = false;
-                                                } else {
-                                                    isLiteral = true;
-                                                }
-                                            }
-                                        } while (c);
-                                        break;
-                                    case '.':
-                                        if (option.regexp) {
-                                            warningAt("Insecure '{a}'.", line,
-                                                    from + l, c);
-                                        }
-                                        break;
-                                    case ']':
-                                    case '?':
-                                    case '{':
-                                    case '}':
-                                    case '+':
-                                    case '*':
-                                        warningAt("Unescaped '{a}'.", line,
-                                                from + l, c);
-                                    }
-                                    if (b) {
-                                        switch (s.charAt(l)) {
-                                        case '?':
-                                        case '+':
-                                        case '*':
-                                            l += 1;
-                                            if (s.charAt(l) === '?') {
-                                                l += 1;
-                                            }
-                                            break;
-                                        case '{':
-                                            l += 1;
-                                            c = s.charAt(l);
-                                            if (c < '0' || c > '9') {
-                                                warningAt(
-"Expected a number and instead saw '{a}'.", line, from + l, c);
-                                            }
-                                            l += 1;
-                                            low = +c;
-                                            for (;;) {
-                                                c = s.charAt(l);
-                                                if (c < '0' || c > '9') {
-                                                    break;
-                                                }
-                                                l += 1;
-                                                low = +c + (low * 10);
-                                            }
-                                            high = low;
-                                            if (c === ',') {
-                                                l += 1;
-                                                high = Infinity;
-                                                c = s.charAt(l);
-                                                if (c >= '0' && c <= '9') {
-                                                    l += 1;
-                                                    high = +c;
-                                                    for (;;) {
-                                                        c = s.charAt(l);
-                                                        if (c < '0' || c > '9') {
-                                                            break;
-                                                        }
-                                                        l += 1;
-                                                        high = +c + (high * 10);
-                                                    }
-                                                }
-                                            }
-                                            if (s.charAt(l) !== '}') {
-                                                warningAt(
-"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c);
-                                            } else {
-                                                l += 1;
-                                            }
-                                            if (s.charAt(l) === '?') {
-                                                l += 1;
-                                            }
-                                            if (low > high) {
-                                                warningAt(
-"'{a}' should not be greater than '{b}'.", line, from + l, low, high);
-                                            }
-                                        }
-                                    }
-                                }
-                                c = s.substr(0, l - 1);
-                                character += l;
-                                s = s.substr(l);
-                                return it('(regexp)', c);
-                            }
-                            return it('(punctuator)', t);
-
-    //      punctuator
-
-                        case '#':
-                            return it('(punctuator)', t);
-                        default:
-                            return it('(punctuator)', t);
-                        }
-                    }
-                }
-            }
-        };
-    }());
-
-
-    function addlabel(t, type) {
-
-        if (t === 'hasOwnProperty') {
-            warning("'hasOwnProperty' is a really bad name.");
-        }
-
-// Define t in the current function in the current scope.
-        if (is_own(funct, t) && !funct['(global)']) {
-            if (funct[t] === true) {
-                if (option.latedef)
-                    warning("'{a}' was used before it was defined.", nexttoken, t);
-            } else {
-                if (!option.shadow && type !== "exception")
-                    warning("'{a}' is already defined.", nexttoken, t);
-            }
-        }
-
-        funct[t] = type;
-        if (funct['(global)']) {
-            global[t] = funct;
-            if (is_own(implied, t)) {
-                if (option.latedef)
-                    warning("'{a}' was used before it was defined.", nexttoken, t);
-                delete implied[t];
-            }
-        } else {
-            scope[t] = funct;
-        }
-    }
-
-
-    function doOption() {
-        var b, obj, filter, o = nexttoken.value, t, v;
-        switch (o) {
-        case '*/':
-            error("Unbegun comment.");
-            break;
-        case '/*members':
-        case '/*member':
-            o = '/*members';
-            if (!membersOnly) {
-                membersOnly = {};
-            }
-            obj = membersOnly;
-            break;
-        case '/*jshint':
-        case '/*jslint':
-            obj = option;
-            filter = boolOptions;
-            break;
-        case '/*global':
-            obj = predefined;
-            break;
-        default:
-            error("What?");
-        }
-        t = lex.token();
-loop:   for (;;) {
-            for (;;) {
-                if (t.type === 'special' && t.value === '*/') {
-                    break loop;
-                }
-                if (t.id !== '(endline)' && t.id !== ',') {
-                    break;
-                }
-                t = lex.token();
-            }
-            if (t.type !== '(string)' && t.type !== '(identifier)' &&
-                    o !== '/*members') {
-                error("Bad option.", t);
-            }
-            v = lex.token();
-            if (v.id === ':') {
-                v = lex.token();
-                if (obj === membersOnly) {
-                    error("Expected '{a}' and instead saw '{b}'.",
-                            t, '*/', ':');
-                }
-                if (t.value === 'indent' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.white = true;
-                    obj.indent = b;
-                } else if (t.value === 'maxerr' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.maxerr = b;
-                } else if (t.value === 'maxlen' && (o === '/*jshint' || o === '/*jslint')) {
-                    b = +v.value;
-                    if (typeof b !== 'number' || !isFinite(b) || b <= 0 ||
-                            Math.floor(b) !== b) {
-                        error("Expected a small integer and instead saw '{a}'.",
-                                v, v.value);
-                    }
-                    obj.maxlen = b;
-                } else if (t.value === 'validthis') {
-                    if (funct['(global)']) {
-                        error("Option 'validthis' can't be used in a global scope.");
-                    } else {
-                        if (v.value === 'true' || v.value === 'false')
-                            obj[t.value] = v.value === 'true';
-                        else
-                            error("Bad option value.", v);
-                    }
-                } else if (v.value === 'true') {
-                    obj[t.value] = true;
-                } else if (v.value === 'false') {
-                    obj[t.value] = false;
-                } else {
-                    error("Bad option value.", v);
-                }
-                t = lex.token();
-            } else {
-                if (o === '/*jshint' || o === '/*jslint') {
-                    error("Missing option value.", t);
-                }
-                obj[t.value] = false;
-                t = v;
-            }
-        }
-        if (filter) {
-            assume();
-        }
-    }
-
-
-// We need a peek function. If it has an argument, it peeks that much farther
-// ahead. It is used to distinguish
-//     for ( var i in ...
-// from
-//     for ( var i = ...
-
-    function peek(p) {
-        var i = p || 0, j = 0, t;
-
-        while (j <= i) {
-            t = lookahead[j];
-            if (!t) {
-                t = lookahead[j] = lex.token();
-            }
-            j += 1;
-        }
-        return t;
-    }
-
-
-
-// Produce the next token. It looks for programming errors.
-
-    function advance(id, t) {
-        switch (token.id) {
-        case '(number)':
-            if (nexttoken.id === '.') {
-                warning("A dot following a number can be confused with a decimal point.", token);
-            }
-            break;
-        case '-':
-            if (nexttoken.id === '-' || nexttoken.id === '--') {
-                warning("Confusing minusses.");
-            }
-            break;
-        case '+':
-            if (nexttoken.id === '+' || nexttoken.id === '++') {
-                warning("Confusing plusses.");
-            }
-            break;
-        }
-
-        if (token.type === '(string)' || token.identifier) {
-            anonname = token.value;
-        }
-
-        if (id && nexttoken.id !== id) {
-            if (t) {
-                if (nexttoken.id === '(end)') {
-                    warning("Unmatched '{a}'.", t, t.id);
-                } else {
-                    warning("Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",
-                            nexttoken, id, t.id, t.line, nexttoken.value);
-                }
-            } else if (nexttoken.type !== '(identifier)' ||
-                            nexttoken.value !== id) {
-                warning("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, id, nexttoken.value);
-            }
-        }
-
-        prevtoken = token;
-        token = nexttoken;
-        for (;;) {
-            nexttoken = lookahead.shift() || lex.token();
-            if (nexttoken.id === '(end)' || nexttoken.id === '(error)') {
-                return;
-            }
-            if (nexttoken.type === 'special') {
-                doOption();
-            } else {
-                if (nexttoken.id !== '(endline)') {
-                    break;
-                }
-            }
-        }
-    }
-
-
-// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it
-// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is
-// like .nud except that it is only used on the first token of a statement.
-// Having .fud makes it much easier to define statement-oriented languages like
-// JavaScript. I retained Pratt's nomenclature.
-
-// .nud     Null denotation
-// .fud     First null denotation
-// .led     Left denotation
-//  lbp     Left binding power
-//  rbp     Right binding power
-
-// They are elements of the parsing method called Top Down Operator Precedence.
-
-    function expression(rbp, initial) {
-        var left, isArray = false;
-
-        if (nexttoken.id === '(end)')
-            error("Unexpected early end of program.", token);
-
-        advance();
-        if (initial) {
-            anonname = 'anonymous';
-            funct['(verb)'] = token.value;
-        }
-        if (initial === true && token.fud) {
-            left = token.fud();
-        } else {
-            if (token.nud) {
-                left = token.nud();
-            } else {
-                if (nexttoken.type === '(number)' && token.id === '.') {
-                    warning("A leading decimal point can be confused with a dot: '.{a}'.",
-                            token, nexttoken.value);
-                    advance();
-                    return token;
-                } else {
-                    error("Expected an identifier and instead saw '{a}'.",
-                            token, token.id);
-                }
-            }
-            while (rbp < nexttoken.lbp) {
-                isArray = token.value === 'Array';
-                advance();
-                if (isArray && token.id === '(' && nexttoken.id === ')')
-                    warning("Use the array literal notation [].", token);
-                if (token.led) {
-                    left = token.led(left);
-                } else {
-                    error("Expected an operator and instead saw '{a}'.",
-                        token, token.id);
-                }
-            }
-        }
-        return left;
-    }
-
-
-// Functions for conformance of style.
-
-    function adjacent(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white) {
-            if (left.character !== right.from && left.line === right.line) {
-                left.from += (left.character - left.from);
-                warning("Unexpected space after '{a}'.", left, left.value);
-            }
-        }
-    }
-
-    function nobreak(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white && (left.character !== right.from || left.line !== right.line)) {
-            warning("Unexpected space before '{a}'.", right, right.value);
-        }
-    }
-
-    function nospace(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (option.white && !left.comment) {
-            if (left.line === right.line) {
-                adjacent(left, right);
-            }
-        }
-    }
-
-    function nonadjacent(left, right) {
-        if (option.white) {
-            left = left || token;
-            right = right || nexttoken;
-            if (left.line === right.line && left.character === right.from) {
-                left.from += (left.character - left.from);
-                warning("Missing space after '{a}'.",
-                        left, left.value);
-            }
-        }
-    }
-
-    function nobreaknonadjacent(left, right) {
-        left = left || token;
-        right = right || nexttoken;
-        if (!option.laxbreak && left.line !== right.line) {
-            warning("Bad line breaking before '{a}'.", right, right.id);
-        } else if (option.white) {
-            left = left || token;
-            right = right || nexttoken;
-            if (left.character === right.from) {
-                left.from += (left.character - left.from);
-                warning("Missing space after '{a}'.",
-                        left, left.value);
-            }
-        }
-    }
-
-    function indentation(bias) {
-        var i;
-        if (option.white && nexttoken.id !== '(end)') {
-            i = indent + (bias || 0);
-            if (nexttoken.from !== i) {
-                warning(
-"Expected '{a}' to have an indentation at {b} instead at {c}.",
-                        nexttoken, nexttoken.value, i, nexttoken.from);
-            }
-        }
-    }
-
-    function nolinebreak(t) {
-        t = t || token;
-        if (t.line !== nexttoken.line) {
-            warning("Line breaking error '{a}'.", t, t.value);
-        }
-    }
-
-
-    function comma() {
-        if (token.line !== nexttoken.line) {
-            if (!option.laxbreak) {
-                warning("Bad line breaking before '{a}'.", token, nexttoken.id);
-            }
-        } else if (!token.comment && token.character !== nexttoken.from && option.white) {
-            token.from += (token.character - token.from);
-            warning("Unexpected space after '{a}'.", token, token.value);
-        }
-        advance(',');
-        nonadjacent(token, nexttoken);
-    }
-
-
-// Functional constructors for making the symbols that will be inherited by
-// tokens.
-
-    function symbol(s, p) {
-        var x = syntax[s];
-        if (!x || typeof x !== 'object') {
-            syntax[s] = x = {
-                id: s,
-                lbp: p,
-                value: s
-            };
-        }
-        return x;
-    }
-
-
-    function delim(s) {
-        return symbol(s, 0);
-    }
-
-
-    function stmt(s, f) {
-        var x = delim(s);
-        x.identifier = x.reserved = true;
-        x.fud = f;
-        return x;
-    }
-
-
-    function blockstmt(s, f) {
-        var x = stmt(s, f);
-        x.block = true;
-        return x;
-    }
-
-
-    function reserveName(x) {
-        var c = x.id.charAt(0);
-        if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
-            x.identifier = x.reserved = true;
-        }
-        return x;
-    }
-
-
-    function prefix(s, f) {
-        var x = symbol(s, 150);
-        reserveName(x);
-        x.nud = (typeof f === 'function') ? f : function () {
-            this.right = expression(150);
-            this.arity = 'unary';
-            if (this.id === '++' || this.id === '--') {
-                if (option.plusplus) {
-                    warning("Unexpected use of '{a}'.", this, this.id);
-                } else if ((!this.right.identifier || this.right.reserved) &&
-                        this.right.id !== '.' && this.right.id !== '[') {
-                    warning("Bad operand.", this);
-                }
-            }
-            return this;
-        };
-        return x;
-    }
-
-
-    function type(s, f) {
-        var x = delim(s);
-        x.type = s;
-        x.nud = f;
-        return x;
-    }
-
-
-    function reserve(s, f) {
-        var x = type(s, f);
-        x.identifier = x.reserved = true;
-        return x;
-    }
-
-
-    function reservevar(s, v) {
-        return reserve(s, function () {
-            if (typeof v === 'function') {
-                v(this);
-            }
-            return this;
-        });
-    }
-
-
-    function infix(s, f, p, w) {
-        var x = symbol(s, p);
-        reserveName(x);
-        x.led = function (left) {
-            if (!w) {
-                nobreaknonadjacent(prevtoken, token);
-                nonadjacent(token, nexttoken);
-            }
-            if (s === "in" && left.id === "!") {
-                warning("Confusing use of '{a}'.", left, '!');
-            }
-            if (typeof f === 'function') {
-                return f(left, this);
-            } else {
-                this.left = left;
-                this.right = expression(p);
-                return this;
-            }
-        };
-        return x;
-    }
-
-
-    function relation(s, f) {
-        var x = symbol(s, 100);
-        x.led = function (left) {
-            nobreaknonadjacent(prevtoken, token);
-            nonadjacent(token, nexttoken);
-            var right = expression(100);
-            if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) {
-                warning("Use the isNaN function to compare with NaN.", this);
-            } else if (f) {
-                f.apply(this, [left, right]);
-            }
-            if (left.id === '!') {
-                warning("Confusing use of '{a}'.", left, '!');
-            }
-            if (right.id === '!') {
-                warning("Confusing use of '{a}'.", right, '!');
-            }
-            this.left = left;
-            this.right = right;
-            return this;
-        };
-        return x;
-    }
-
-
-    function isPoorRelation(node) {
-        return node &&
-              ((node.type === '(number)' && +node.value === 0) ||
-               (node.type === '(string)' && node.value === '') ||
-               (node.type === 'null' && !option.eqnull) ||
-                node.type === 'true' ||
-                node.type === 'false' ||
-                node.type === 'undefined');
-    }
-
-
-    function assignop(s, f) {
-        symbol(s, 20).exps = true;
-        return infix(s, function (left, that) {
-            var l;
-            that.left = left;
-            if (predefined[left.value] === false &&
-                    scope[left.value]['(global)'] === true) {
-                warning("Read only.", left);
-            } else if (left['function']) {
-                warning("'{a}' is a function.", left, left.value);
-            }
-            if (left) {
-                if (option.esnext && funct[left.value] === 'const') {
-                    warning("Attempting to override '{a}' which is a constant", left, left.value);
-                }
-                if (left.id === '.' || left.id === '[') {
-                    if (!left.left || left.left.value === 'arguments') {
-                        warning('Bad assignment.', that);
-                    }
-                    that.right = expression(19);
-                    return that;
-                } else if (left.identifier && !left.reserved) {
-                    if (funct[left.value] === 'exception') {
-                        warning("Do not assign to the exception parameter.", left);
-                    }
-                    that.right = expression(19);
-                    return that;
-                }
-                if (left === syntax['function']) {
-                    warning(
-"Expected an identifier in an assignment and instead saw a function invocation.",
-                                token);
-                }
-            }
-            error("Bad assignment.", that);
-        }, 20);
-    }
-
-
-    function bitwise(s, f, p) {
-        var x = symbol(s, p);
-        reserveName(x);
-        x.led = (typeof f === 'function') ? f : function (left) {
-            if (option.bitwise) {
-                warning("Unexpected use of '{a}'.", this, this.id);
-            }
-            this.left = left;
-            this.right = expression(p);
-            return this;
-        };
-        return x;
-    }
-
-
-    function bitwiseassignop(s) {
-        symbol(s, 20).exps = true;
-        return infix(s, function (left, that) {
-            if (option.bitwise) {
-                warning("Unexpected use of '{a}'.", that, that.id);
-            }
-            nonadjacent(prevtoken, token);
-            nonadjacent(token, nexttoken);
-            if (left) {
-                if (left.id === '.' || left.id === '[' ||
-                        (left.identifier && !left.reserved)) {
-                    expression(19);
-                    return that;
-                }
-                if (left === syntax['function']) {
-                    warning(
-"Expected an identifier in an assignment, and instead saw a function invocation.",
-                                token);
-                }
-                return that;
-            }
-            error("Bad assignment.", that);
-        }, 20);
-    }
-
-
-    function suffix(s, f) {
-        var x = symbol(s, 150);
-        x.led = function (left) {
-            if (option.plusplus) {
-                warning("Unexpected use of '{a}'.", this, this.id);
-            } else if ((!left.identifier || left.reserved) &&
-                    left.id !== '.' && left.id !== '[') {
-                warning("Bad operand.", this);
-            }
-            this.left = left;
-            return this;
-        };
-        return x;
-    }
-
-
-    // fnparam means that this identifier is being defined as a function
-    // argument (see identifier())
-    function optionalidentifier(fnparam) {
-        if (nexttoken.identifier) {
-            advance();
-            if (token.reserved && !option.es5) {
-                // `undefined` as a function param is a common pattern to protect
-                // against the case when somebody does `undefined = true` and
-                // help with minification. More info: https://gist.github.com/315916
-                if (!fnparam || token.value !== 'undefined') {
-                    warning("Expected an identifier and instead saw '{a}' (a reserved word).",
-                            token, token.id);
-                }
-            }
-            return token.value;
-        }
-    }
-
-    // fnparam means that this identifier is being defined as a function
-    // argument
-    function identifier(fnparam) {
-        var i = optionalidentifier(fnparam);
-        if (i) {
-            return i;
-        }
-        if (token.id === 'function' && nexttoken.id === '(') {
-            warning("Missing name in function declaration.");
-        } else {
-            error("Expected an identifier and instead saw '{a}'.",
-                    nexttoken, nexttoken.value);
-        }
-    }
-
-
-    function reachable(s) {
-        var i = 0, t;
-        if (nexttoken.id !== ';' || noreach) {
-            return;
-        }
-        for (;;) {
-            t = peek(i);
-            if (t.reach) {
-                return;
-            }
-            if (t.id !== '(endline)') {
-                if (t.id === 'function') {
-                    warning(
-"Inner functions should be listed at the top of the outer function.", t);
-                    break;
-                }
-                warning("Unreachable '{a}' after '{b}'.", t, t.value, s);
-                break;
-            }
-            i += 1;
-        }
-    }
-
-
-    function statement(noindent) {
-        var i = indent, r, s = scope, t = nexttoken;
-
-        if (t.id === ";") {
-            advance(";");
-            return;
-        }
-
-// Is this a labelled statement?
-
-        if (t.identifier && !t.reserved && peek().id === ':') {
-            advance();
-            advance(':');
-            scope = Object.create(s);
-            addlabel(t.value, 'label');
-            if (!nexttoken.labelled) {
-                warning("Label '{a}' on {b} statement.",
-                        nexttoken, t.value, nexttoken.value);
-            }
-            if (jx.test(t.value + ':')) {
-                warning("Label '{a}' looks like a javascript url.",
-                        t, t.value);
-            }
-            nexttoken.label = t.value;
-            t = nexttoken;
-        }
-
-// Parse the statement.
-
-        if (!noindent) {
-            indentation();
-        }
-        r = expression(0, true);
-
-        // Look for the final semicolon.
-        if (!t.block) {
-            if (!option.expr && (!r || !r.exps)) {
-                warning("Expected an assignment or function call and instead saw an expression.",
-                    token);
-            } else if (option.nonew && r.id === '(' && r.left.id === 'new') {
-                warning("Do not use 'new' for side effects.");
-            }
-
-            if (nexttoken.id !== ';') {
-                if (!option.asi) {
-                    // If this is the last statement in a block that ends on
-                    // the same line *and* option lastsemic is on, ignore the warning.
-                    // Otherwise, complain about missing semicolon.
-                    if (!option.lastsemic || nexttoken.id !== '}' ||
-                            nexttoken.line !== token.line) {
-                        warningAt("Missing semicolon.", token.line, token.character);
-                    }
-                }
-            } else {
-                adjacent(token, nexttoken);
-                advance(';');
-                nonadjacent(token, nexttoken);
-            }
-        }
-
-// Restore the indentation.
-
-        indent = i;
-        scope = s;
-        return r;
-    }
-
-
-    function statements(startLine) {
-        var a = [], f, p;
-
-        while (!nexttoken.reach && nexttoken.id !== '(end)') {
-            if (nexttoken.id === ';') {
-                warning("Unnecessary semicolon.");
-                advance(';');
-            } else {
-                a.push(statement(startLine === nexttoken.line));
-            }
-        }
-        return a;
-    }
-
-
-    /*
-     * read all directives
-     * recognizes a simple form of asi, but always
-     * warns, if it is used
-     */
-    function directives() {
-        var i, p, pn;
-
-        for (;;) {
-            if (nexttoken.id === "(string)") {
-                p = peek(0);
-                if (p.id === "(endline)") {
-                    i = 1;
-                    do {
-                        pn = peek(i);
-                        i = i + 1;
-                    } while (pn.id === "(endline)");
-
-                    if (pn.id !== ";") {
-                        if (pn.id !== "(string)" && pn.id !== "(number)" &&
-                            pn.id !== "(regexp)" && pn.identifier !== true &&
-                            pn.id !== "}") {
-                            break;
-                        }
-                        warning("Missing semicolon.", nexttoken);
-                    } else {
-                        p = pn;
-                    }
-                } else if (p.id === "}") {
-                    // directive with no other statements, warn about missing semicolon
-                    warning("Missing semicolon.", p);
-                } else if (p.id !== ";") {
-                    break;
-                }
-
-                indentation();
-                advance();
-                if (directive[token.value]) {
-                    warning("Unnecessary directive \"{a}\".", token, token.value);
-                }
-
-                if (token.value === "use strict") {
-                    option.newcap = true;
-                    option.undef = true;
-                }
-
-                // there's no directive negation, so always set to true
-                directive[token.value] = true;
-
-                if (p.id === ";") {
-                    advance(";");
-                }
-                continue;
-            }
-            break;
-        }
-    }
-
-
-    /*
-     * Parses a single block. A block is a sequence of statements wrapped in
-     * braces.
-     *
-     * ordinary - true for everything but function bodies and try blocks.
-     * stmt     - true if block can be a single statement (e.g. in if/for/while).
-     * isfunc   - true if block is a function body
-     */
-    function block(ordinary, stmt, isfunc) {
-        var a,
-            b = inblock,
-            old_indent = indent,
-            m,
-            s = scope,
-            t,
-            line,
-            d;
-
-        inblock = ordinary;
-        if (!ordinary || !option.funcscope) scope = Object.create(scope);
-        nonadjacent(token, nexttoken);
-        t = nexttoken;
-
-        if (nexttoken.id === '{') {
-            advance('{');
-            line = token.line;
-            if (nexttoken.id !== '}') {
-                indent += option.indent;
-                while (!ordinary && nexttoken.from > indent) {
-                    indent += option.indent;
-                }
-
-                if (isfunc) {
-                    m = {};
-                    for (d in directive) {
-                        if (is_own(directive, d)) {
-                            m[d] = directive[d];
-                        }
-                    }
-                    directives();
-
-                    if (option.strict && funct['(context)']['(global)']) {
-                        if (!m["use strict"] && !directive["use strict"]) {
-                            warning("Missing \"use strict\" statement.");
-                        }
-                    }
-                }
-
-                a = statements(line);
-
-                if (isfunc) {
-                    directive = m;
-                }
-
-                indent -= option.indent;
-                if (line !== nexttoken.line) {
-                    indentation();
-                }
-            } else if (line !== nexttoken.line) {
-                indentation();
-            }
-            advance('}', t);
-            indent = old_indent;
-        } else if (!ordinary) {
-            error("Expected '{a}' and instead saw '{b}'.",
-                  nexttoken, '{', nexttoken.value);
-        } else {
-            if (!stmt || option.curly)
-                warning("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, '{', nexttoken.value);
-
-            noreach = true;
-            indent += option.indent;
-            // test indentation only if statement is in new line
-            a = [statement(nexttoken.line === token.line)];
-            indent -= option.indent;
-            noreach = false;
-        }
-        funct['(verb)'] = null;
-        if (!ordinary || !option.funcscope) scope = s;
-        inblock = b;
-        if (ordinary && option.noempty && (!a || a.length === 0)) {
-            warning("Empty block.");
-        }
-        return a;
-    }
-
-
-    function countMember(m) {
-        if (membersOnly && typeof membersOnly[m] !== 'boolean') {
-            warning("Unexpected /*member '{a}'.", token, m);
-        }
-        if (typeof member[m] === 'number') {
-            member[m] += 1;
-        } else {
-            member[m] = 1;
-        }
-    }
-
-
-    function note_implied(token) {
-        var name = token.value, line = token.line, a = implied[name];
-        if (typeof a === 'function') {
-            a = false;
-        }
-        if (!a) {
-            a = [line];
-            implied[name] = a;
-        } else if (a[a.length - 1] !== line) {
-            a.push(line);
-        }
-    }
-
-
-    // Build the syntax table by declaring the syntactic elements of the language.
-
-    type('(number)', function () {
-        return this;
-    });
-
-    type('(string)', function () {
-        return this;
-    });
-
-    syntax['(identifier)'] = {
-        type: '(identifier)',
-        lbp: 0,
-        identifier: true,
-        nud: function () {
-            var v = this.value,
-                s = scope[v],
-                f;
-
-            if (typeof s === 'function') {
-                // Protection against accidental inheritance.
-                s = undefined;
-            } else if (typeof s === 'boolean') {
-                f = funct;
-                funct = functions[0];
-                addlabel(v, 'var');
-                s = funct;
-                funct = f;
-            }
-
-            // The name is in scope and defined in the current function.
-            if (funct === s) {
-                // Change 'unused' to 'var', and reject labels.
-                switch (funct[v]) {
-                case 'unused':
-                    funct[v] = 'var';
-                    break;
-                case 'unction':
-                    funct[v] = 'function';
-                    this['function'] = true;
-                    break;
-                case 'function':
-                    this['function'] = true;
-                    break;
-                case 'label':
-                    warning("'{a}' is a statement label.", token, v);
-                    break;
-                }
-            } else if (funct['(global)']) {
-                // The name is not defined in the function.  If we are in the global
-                // scope, then we have an undefined variable.
-                //
-                // Operators typeof and delete do not raise runtime errors even if
-                // the base object of a reference is null so no need to display warning
-                // if we're inside of typeof or delete.
-                if (anonname !== 'typeof' && anonname !== 'delete' &&
-                    option.undef && typeof predefined[v] !== 'boolean') {
-                    isundef(funct, "'{a}' is not defined.", token, v);
-                }
-                note_implied(token);
-            } else {
-                // If the name is already defined in the current
-                // function, but not as outer, then there is a scope error.
-
-                switch (funct[v]) {
-                case 'closure':
-                case 'function':
-                case 'var':
-                case 'unused':
-                    warning("'{a}' used out of scope.", token, v);
-                    break;
-                case 'label':
-                    warning("'{a}' is a statement label.", token, v);
-                    break;
-                case 'outer':
-                case 'global':
-                    break;
-                default:
-                    // If the name is defined in an outer function, make an outer entry,
-                    // and if it was unused, make it var.
-                    if (s === true) {
-                        funct[v] = true;
-                    } else if (s === null) {
-                        warning("'{a}' is not allowed.", token, v);
-                        note_implied(token);
-                    } else if (typeof s !== 'object') {
-                        // Operators typeof and delete do not raise runtime errors even
-                        // if the base object of a reference is null so no need to
-                        // display warning if we're inside of typeof or delete.
-                        if (anonname !== 'typeof' && anonname !== 'delete' && option.undef) {
-                            isundef(funct, "'{a}' is not defined.", token, v);
-                        }
-                        funct[v] = true;
-                        note_implied(token);
-                    } else {
-                        switch (s[v]) {
-                        case 'function':
-                        case 'unction':
-                            this['function'] = true;
-                            s[v] = 'closure';
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'var':
-                        case 'unused':
-                            s[v] = 'closure';
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'closure':
-                        case 'parameter':
-                            funct[v] = s['(global)'] ? 'global' : 'outer';
-                            break;
-                        case 'label':
-                            warning("'{a}' is a statement label.", token, v);
-                        }
-                    }
-                }
-            }
-            return this;
-        },
-        led: function () {
-            error("Expected an operator and instead saw '{a}'.",
-                nexttoken, nexttoken.value);
-        }
-    };
-
-    type('(regexp)', function () {
-        return this;
-    });
-
-
-// ECMAScript parser
-
-    delim('(endline)');
-    delim('(begin)');
-    delim('(end)').reach = true;
-    delim('</').reach = true;
-    delim('<!');
-    delim('<!--');
-    delim('-->');
-    delim('(error)').reach = true;
-    delim('}').reach = true;
-    delim(')');
-    delim(']');
-    delim('"').reach = true;
-    delim("'").reach = true;
-    delim(';');
-    delim(':').reach = true;
-    delim(',');
-    delim('#');
-    delim('@');
-    reserve('else');
-    reserve('case').reach = true;
-    reserve('catch');
-    reserve('default').reach = true;
-    reserve('finally');
-    reservevar('arguments', function (x) {
-        if (directive['use strict'] && funct['(global)']) {
-            warning("Strict violation.", x);
-        }
-    });
-    reservevar('eval');
-    reservevar('false');
-    reservevar('Infinity');
-    reservevar('NaN');
-    reservevar('null');
-    reservevar('this', function (x) {
-        if (directive['use strict'] && !option.validthis && ((funct['(statement)'] &&
-                funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) {
-            warning("Possible strict violation.", x);
-        }
-    });
-    reservevar('true');
-    reservevar('undefined');
-    assignop('=', 'assign', 20);
-    assignop('+=', 'assignadd', 20);
-    assignop('-=', 'assignsub', 20);
-    assignop('*=', 'assignmult', 20);
-    assignop('/=', 'assigndiv', 20).nud = function () {
-        error("A regular expression literal can be confused with '/='.");
-    };
-    assignop('%=', 'assignmod', 20);
-    bitwiseassignop('&=', 'assignbitand', 20);
-    bitwiseassignop('|=', 'assignbitor', 20);
-    bitwiseassignop('^=', 'assignbitxor', 20);
-    bitwiseassignop('<<=', 'assignshiftleft', 20);
-    bitwiseassignop('>>=', 'assignshiftright', 20);
-    bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20);
-    infix('?', function (left, that) {
-        that.left = left;
-        that.right = expression(10);
-        advance(':');
-        that['else'] = expression(10);
-        return that;
-    }, 30);
-
-    infix('||', 'or', 40);
-    infix('&&', 'and', 50);
-    bitwise('|', 'bitor', 70);
-    bitwise('^', 'bitxor', 80);
-    bitwise('&', 'bitand', 90);
-    relation('==', function (left, right) {
-        var eqnull = option.eqnull && (left.value === 'null' || right.value === 'null');
-
-        if (!eqnull && option.eqeqeq)
-            warning("Expected '{a}' and instead saw '{b}'.", this, '===', '==');
-        else if (isPoorRelation(left))
-            warning("Use '{a}' to compare with '{b}'.", this, '===', left.value);
-        else if (isPoorRelation(right))
-            warning("Use '{a}' to compare with '{b}'.", this, '===', right.value);
-
-        return this;
-    });
-    relation('===');
-    relation('!=', function (left, right) {
-        var eqnull = option.eqnull &&
-                (left.value === 'null' || right.value === 'null');
-
-        if (!eqnull && option.eqeqeq) {
-            warning("Expected '{a}' and instead saw '{b}'.",
-                    this, '!==', '!=');
-        } else if (isPoorRelation(left)) {
-            warning("Use '{a}' to compare with '{b}'.",
-                    this, '!==', left.value);
-        } else if (isPoorRelation(right)) {
-            warning("Use '{a}' to compare with '{b}'.",
-                    this, '!==', right.value);
-        }
-        return this;
-    });
-    relation('!==');
-    relation('<');
-    relation('>');
-    relation('<=');
-    relation('>=');
-    bitwise('<<', 'shiftleft', 120);
-    bitwise('>>', 'shiftright', 120);
-    bitwise('>>>', 'shiftrightunsigned', 120);
-    infix('in', 'in', 120);
-    infix('instanceof', 'instanceof', 120);
-    infix('+', function (left, that) {
-        var right = expression(130);
-        if (left && right && left.id === '(string)' && right.id === '(string)') {
-            left.value += right.value;
-            left.character = right.character;
-            if (!option.scripturl && jx.test(left.value)) {
-                warning("JavaScript URL.", left);
-            }
-            return left;
-        }
-        that.left = left;
-        that.right = right;
-        return that;
-    }, 130);
-    prefix('+', 'num');
-    prefix('+++', function () {
-        warning("Confusing pluses.");
-        this.right = expression(150);
-        this.arity = 'unary';
-        return this;
-    });
-    infix('+++', function (left) {
-        warning("Confusing pluses.");
-        this.left = left;
-        this.right = expression(130);
-        return this;
-    }, 130);
-    infix('-', 'sub', 130);
-    prefix('-', 'neg');
-    prefix('---', function () {
-        warning("Confusing minuses.");
-        this.right = expression(150);
-        this.arity = 'unary';
-        return this;
-    });
-    infix('---', function (left) {
-        warning("Confusing minuses.");
-        this.left = left;
-        this.right = expression(130);
-        return this;
-    }, 130);
-    infix('*', 'mult', 140);
-    infix('/', 'div', 140);
-    infix('%', 'mod', 140);
-
-    suffix('++', 'postinc');
-    prefix('++', 'preinc');
-    syntax['++'].exps = true;
-
-    suffix('--', 'postdec');
-    prefix('--', 'predec');
-    syntax['--'].exps = true;
-    prefix('delete', function () {
-        var p = expression(0);
-        if (!p || (p.id !== '.' && p.id !== '[')) {
-            warning("Variables should not be deleted.");
-        }
-        this.first = p;
-        return this;
-    }).exps = true;
-
-    prefix('~', function () {
-        if (option.bitwise) {
-            warning("Unexpected '{a}'.", this, '~');
-        }
-        expression(150);
-        return this;
-    });
-
-    prefix('!', function () {
-        this.right = expression(150);
-        this.arity = 'unary';
-        if (bang[this.right.id] === true) {
-            warning("Confusing use of '{a}'.", this, '!');
-        }
-        return this;
-    });
-    prefix('typeof', 'typeof');
-    prefix('new', function () {
-        var c = expression(155), i;
-        if (c && c.id !== 'function') {
-            if (c.identifier) {
-                c['new'] = true;
-                switch (c.value) {
-                case 'Object':
-                    warning("Use the object literal notation {}.", token);
-                    break;
-                case 'Number':
-                case 'String':
-                case 'Boolean':
-                case 'Math':
-                case 'JSON':
-                    warning("Do not use {a} as a constructor.", token, c.value);
-                    break;
-                case 'Function':
-                    if (!option.evil) {
-                        warning("The Function constructor is eval.");
-                    }
-                    break;
-                case 'Date':
-                case 'RegExp':
-                    break;
-                default:
-                    if (c.id !== 'function') {
-                        i = c.value.substr(0, 1);
-                        if (option.newcap && (i < 'A' || i > 'Z')) {
-                            warning("A constructor name should start with an uppercase letter.",
-                                token);
-                        }
-                    }
-                }
-            } else {
-                if (c.id !== '.' && c.id !== '[' && c.id !== '(') {
-                    warning("Bad constructor.", token);
-                }
-            }
-        } else {
-            if (!option.supernew)
-                warning("Weird construction. Delete 'new'.", this);
-        }
-        adjacent(token, nexttoken);
-        if (nexttoken.id !== '(' && !option.supernew) {
-            warning("Missing '()' invoking a constructor.");
-        }
-        this.first = c;
-        return this;
-    });
-    syntax['new'].exps = true;
-
-    prefix('void').exps = true;
-
-    infix('.', function (left, that) {
-        adjacent(prevtoken, token);
-        nobreak();
-        var m = identifier();
-        if (typeof m === 'string') {
-            countMember(m);
-        }
-        that.left = left;
-        that.right = m;
-        if (left && left.value === 'arguments' && (m === 'callee' || m === 'caller')) {
-            if (option.noarg)
-                warning("Avoid arguments.{a}.", left, m);
-            else if (directive['use strict'])
-                error('Strict violation.');
-        } else if (!option.evil && left && left.value === 'document' &&
-                (m === 'write' || m === 'writeln')) {
-            warning("document.write can be a form of eval.", left);
-        }
-        if (!option.evil && (m === 'eval' || m === 'execScript')) {
-            warning('eval is evil.');
-        }
-        return that;
-    }, 160, true);
-
-    infix('(', function (left, that) {
-        if (prevtoken.id !== '}' && prevtoken.id !== ')') {
-            nobreak(prevtoken, token);
-        }
-        nospace();
-        if (option.immed && !left.immed && left.id === 'function') {
-            warning("Wrap an immediate function invocation in parentheses " +
-                "to assist the reader in understanding that the expression " +
-                "is the result of a function, and not the function itself.");
-        }
-        var n = 0,
-            p = [];
-        if (left) {
-            if (left.type === '(identifier)') {
-                if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) {
-                    if (left.value !== 'Number' && left.value !== 'String' &&
-                            left.value !== 'Boolean' &&
-                            left.value !== 'Date') {
-                        if (left.value === 'Math') {
-                            warning("Math is not a function.", left);
-                        } else if (option.newcap) {
-                            warning(
-"Missing 'new' prefix when invoking a constructor.", left);
-                        }
-                    }
-                }
-            }
-        }
-        if (nexttoken.id !== ')') {
-            for (;;) {
-                p[p.length] = expression(10);
-                n += 1;
-                if (nexttoken.id !== ',') {
-                    break;
-                }
-                comma();
-            }
-        }
-        advance(')');
-        nospace(prevtoken, token);
-        if (typeof left === 'object') {
-            if (left.value === 'parseInt' && n === 1) {
-                warning("Missing radix parameter.", left);
-            }
-            if (!option.evil) {
-                if (left.value === 'eval' || left.value === 'Function' ||
-                        left.value === 'execScript') {
-                    warning("eval is evil.", left);
-                } else if (p[0] && p[0].id === '(string)' &&
-                       (left.value === 'setTimeout' ||
-                        left.value === 'setInterval')) {
-                    warning(
-    "Implied eval is evil. Pass a function instead of a string.", left);
-                }
-            }
-            if (!left.identifier && left.id !== '.' && left.id !== '[' &&
-                    left.id !== '(' && left.id !== '&&' && left.id !== '||' &&
-                    left.id !== '?') {
-                warning("Bad invocation.", left);
-            }
-        }
-        that.left = left;
-        return that;
-    }, 155, true).exps = true;
-
-    prefix('(', function () {
-        nospace();
-        if (nexttoken.id === 'function') {
-            nexttoken.immed = true;
-        }
-        var v = expression(0);
-        advance(')', this);
-        nospace(prevtoken, token);
-        if (option.immed && v.id === 'function') {
-            if (nexttoken.id === '(') {
-                warning(
-"Move the invocation into the parens that contain the function.", nexttoken);
-            } else {
-                warning(
-"Do not wrap function literals in parens unless they are to be immediately invoked.",
-                        this);
-            }
-        }
-        return v;
-    });
-
-    infix('[', function (left, that) {
-        nobreak(prevtoken, token);
-        nospace();
-        var e = expression(0), s;
-        if (e && e.type === '(string)') {
-            if (!option.evil && (e.value === 'eval' || e.value === 'execScript')) {
-                warning("eval is evil.", that);
-            }
-            countMember(e.value);
-            if (!option.sub && ix.test(e.value)) {
-                s = syntax[e.value];
-                if (!s || !s.reserved) {
-                    warning("['{a}'] is better written in dot notation.",
-                            e, e.value);
-                }
-            }
-        }
-        advance(']', that);
-        nospace(prevtoken, token);
-        that.left = left;
-        that.right = e;
-        return that;
-    }, 160, true);
-
-    prefix('[', function () {
-        var b = token.line !== nexttoken.line;
-        this.first = [];
-        if (b) {
-            indent += option.indent;
-            if (nexttoken.from === indent + option.indent) {
-                indent += option.indent;
-            }
-        }
-        while (nexttoken.id !== '(end)') {
-            while (nexttoken.id === ',') {
-                warning("Extra comma.");
-                advance(',');
-            }
-            if (nexttoken.id === ']') {
-                break;
-            }
-            if (b && token.line !== nexttoken.line) {
-                indentation();
-            }
-            this.first.push(expression(10));
-            if (nexttoken.id === ',') {
-                comma();
-                if (nexttoken.id === ']' && !option.es5) {
-                    warning("Extra comma.", token);
-                    break;
-                }
-            } else {
-                break;
-            }
-        }
-        if (b) {
-            indent -= option.indent;
-            indentation();
-        }
-        advance(']', this);
-        return this;
-    }, 160);
-
-
-    function property_name() {
-        var id = optionalidentifier(true);
-        if (!id) {
-            if (nexttoken.id === '(string)') {
-                id = nexttoken.value;
-                advance();
-            } else if (nexttoken.id === '(number)') {
-                id = nexttoken.value.toString();
-                advance();
-            }
-        }
-        return id;
-    }
-
-
-    function functionparams() {
-        var i, t = nexttoken, p = [];
-        advance('(');
-        nospace();
-        if (nexttoken.id === ')') {
-            advance(')');
-            return;
-        }
-        for (;;) {
-            i = identifier(true);
-            p.push(i);
-            addlabel(i, 'parameter');
-            if (nexttoken.id === ',') {
-                comma();
-            } else {
-                advance(')', t);
-                nospace(prevtoken, token);
-                return p;
-            }
-        }
-    }
-
-
-    function doFunction(i, statement) {
-        var f,
-            oldOption = option,
-            oldScope  = scope;
-
-        option = Object.create(option);
-        scope = Object.create(scope);
-
-        funct = {
-            '(name)'     : i || '"' + anonname + '"',
-            '(line)'     : nexttoken.line,
-            '(context)'  : funct,
-            '(breakage)' : 0,
-            '(loopage)'  : 0,
-            '(scope)'    : scope,
-            '(statement)': statement
-        };
-        f = funct;
-        token.funct = funct;
-        functions.push(funct);
-        if (i) {
-            addlabel(i, 'function');
-        }
-        funct['(params)'] = functionparams();
-
-        block(false, false, true);
-        scope = oldScope;
-        option = oldOption;
-        funct['(last)'] = token.line;
-        funct = funct['(context)'];
-        return f;
-    }
-
-
-    (function (x) {
-        x.nud = function () {
-            var b, f, i, j, p, seen = {}, t;
-            var prop, acc = {}; // Accessor methods
-
-            function saveSetter(name, token) {
-                if (!acc[name]) {
-                    acc[name] = {};
-                }
-                acc[name].setter = true;
-                acc[name].setterToken = token;
-            }
-
-            function saveGetter(name) {
-                if (!acc[name]) {
-                    acc[name] = {};
-                }
-                acc[name].getter = true;
-            }
-
-            b = token.line !== nexttoken.line;
-            if (b) {
-                indent += option.indent;
-                if (nexttoken.from === indent + option.indent) {
-                    indent += option.indent;
-                }
-            }
-            for (;;) {
-                if (nexttoken.id === '}') {
-                    break;
-                }
-                if (b) {
-                    indentation();
-                }
-                if (nexttoken.value === 'get' && peek().id !== ':') {
-                    advance('get');
-                    if (!option.es5) {
-                        error("get/set are ES5 features.");
-                    }
-                    i = property_name();
-                    if (!i) {
-                        error("Missing property name.");
-                    }
-                    saveGetter(i);
-                    t = nexttoken;
-                    adjacent(token, nexttoken);
-                    f = doFunction();
-                    if (!option.loopfunc && funct['(loopage)']) {
-                        warning("Don't make functions within a loop.", t);
-                    }
-                    p = f['(params)'];
-                    if (p) {
-                        warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i);
-                    }
-                    adjacent(token, nexttoken);
-                } else if (nexttoken.value === 'set' && peek().id !== ':') {
-                    advance('set');
-                    if (!option.es5) {
-                        error("get/set are ES5 features.");
-                    }
-                    i = property_name();
-                    if (!i) {
-                        error("Missing property name.");
-                    }
-                    if (acc[i] && acc[i].setter) {
-                        warning("Duplicate member '{a}'.", nexttoken, i);
-                    }
-                    saveSetter(i, nexttoken);
-                    seen[i] = false;
-                    t = nexttoken;
-                    adjacent(token, nexttoken);
-                    f = doFunction();
-                    p = f['(params)'];
-                    if (!p || p.length !== 1 || p[0] !== 'value') {
-                        warning("Expected (value) in set {a} function.", t, i);
-                    }
-                } else {
-                    i = property_name();
-                    if (typeof i !== 'string') {
-                        break;
-                    }
-                    advance(':');
-                    nonadjacent(token, nexttoken);
-                    expression(10);
-                }
-                if (seen[i] === true) {
-                    warning("Duplicate member '{a}'.", nexttoken, i);
-                }
-                seen[i] = true;
-                countMember(i);
-                if (nexttoken.id === ',') {
-                    comma();
-                    if (nexttoken.id === ',') {
-                        warning("Extra comma.", token);
-                    } else if (nexttoken.id === '}' && !option.es5) {
-                        warning("Extra comma.", token);
-                    }
-                } else {
-                    break;
-                }
-            }
-            if (b) {
-                indent -= option.indent;
-                indentation();
-            }
-            advance('}', this);
-
-            // Check for lonely setters if in the ES5 mode.
-            if (option.es5) {
-                for (prop in acc) {
-                    if (acc.hasOwnProperty(prop) && acc[prop].setter && !acc[prop].getter) {
-                        warning("Setter is defined without getter.", acc[prop].setterToken);
-                    }
-                }
-            }
-            return this;
-        };
-        x.fud = function () {
-            error("Expected to see a statement and instead saw a block.", token);
-        };
-    }(delim('{')));
-
-// This Function is called when esnext option is set to true
-// it adds the `const` statement to JSHINT
-
-    useESNextSyntax = function () {
-        var conststatement = stmt('const', function (prefix) {
-            var id, name, value;
-
-            this.first = [];
-            for (;;) {
-                nonadjacent(token, nexttoken);
-                id = identifier();
-                if (funct[id] === "const") {
-                    warning("const '" + id + "' has already been declared");
-                }
-                if (funct['(global)'] && predefined[id] === false) {
-                    warning("Redefinition of '{a}'.", token, id);
-                }
-                addlabel(id, 'const');
-                if (prefix) {
-                    break;
-                }
-                name = token;
-                this.first.push(token);
-
-                if (nexttoken.id !== "=") {
-                    warning("const " +
-                      "'{a}' is initialized to 'undefined'.", token, id);
-                }
-
-                if (nexttoken.id === '=') {
-                    nonadjacent(token, nexttoken);
-                    advance('=');
-                    nonadjacent(token, nexttoken);
-                    if (nexttoken.id === 'undefined') {
-                        warning("It is not necessary to initialize " +
-                          "'{a}' to 'undefined'.", token, id);
-                    }
-                    if (peek(0).id === '=' && nexttoken.identifier) {
-                        error("Constant {a} was not declared correctly.",
-                                nexttoken, nexttoken.value);
-                    }
-                    value = expression(0);
-                    name.first = value;
-                }
-
-                if (nexttoken.id !== ',') {
-                    break;
-                }
-                comma();
-            }
-            return this;
-        });
-        conststatement.exps = true;
-    };
-
-    var varstatement = stmt('var', function (prefix) {
-        // JavaScript does not have block scope. It only has function scope. So,
-        // declaring a variable in a block can have unexpected consequences.
-        var id, name, value;
-
-        if (funct['(onevar)'] && option.onevar) {
-            warning("Too many var statements.");
-        } else if (!funct['(global)']) {
-            funct['(onevar)'] = true;
-        }
-        this.first = [];
-        for (;;) {
-            nonadjacent(token, nexttoken);
-            id = identifier();
-            if (option.esnext && funct[id] === "const") {
-                warning("const '" + id + "' has already been declared");
-            }
-            if (funct['(global)'] && predefined[id] === false) {
-                warning("Redefinition of '{a}'.", token, id);
-            }
-            addlabel(id, 'unused');
-            if (prefix) {
-                break;
-            }
-            name = token;
-            this.first.push(token);
-            if (nexttoken.id === '=') {
-                nonadjacent(token, nexttoken);
-                advance('=');
-                nonadjacent(token, nexttoken);
-                if (nexttoken.id === 'undefined') {
-                    warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id);
-                }
-                if (peek(0).id === '=' && nexttoken.identifier) {
-                    error("Variable {a} was not declared correctly.",
-                            nexttoken, nexttoken.value);
-                }
-                value = expression(0);
-                name.first = value;
-            }
-            if (nexttoken.id !== ',') {
-                break;
-            }
-            comma();
-        }
-        return this;
-    });
-    varstatement.exps = true;
-
-    blockstmt('function', function () {
-        if (inblock) {
-            warning("Function declarations should not be placed in blocks. " +
-                "Use a function expression or move the statement to the top of " +
-                "the outer function.", token);
-
-        }
-        var i = identifier();
-        if (option.esnext && funct[i] === "const") {
-            warning("const '" + i + "' has already been declared");
-        }
-        adjacent(token, nexttoken);
-        addlabel(i, 'unction');
-        doFunction(i, true);
-        if (nexttoken.id === '(' && nexttoken.line === token.line) {
-            error(
-"Function declarations are not invocable. Wrap the whole function invocation in parens.");
-        }
-        return this;
-    });
-
-    prefix('function', function () {
-        var i = optionalidentifier();
-        if (i) {
-            adjacent(token, nexttoken);
-        } else {
-            nonadjacent(token, nexttoken);
-        }
-        doFunction(i);
-        if (!option.loopfunc && funct['(loopage)']) {
-            warning("Don't make functions within a loop.");
-        }
-        return this;
-    });
-
-    blockstmt('if', function () {
-        var t = nexttoken;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        expression(20);
-        if (nexttoken.id === '=') {
-            if (!option.boss)
-                warning("Expected a conditional expression and instead saw an assignment.");
-            advance('=');
-            expression(20);
-        }
-        advance(')', t);
-        nospace(prevtoken, token);
-        block(true, true);
-        if (nexttoken.id === 'else') {
-            nonadjacent(token, nexttoken);
-            advance('else');
-            if (nexttoken.id === 'if' || nexttoken.id === 'switch') {
-                statement(true);
-            } else {
-                block(true, true);
-            }
-        }
-        return this;
-    });
-
-    blockstmt('try', function () {
-        var b, e, s;
-
-        block(false);
-        if (nexttoken.id === 'catch') {
-            advance('catch');
-            nonadjacent(token, nexttoken);
-            advance('(');
-            s = scope;
-            scope = Object.create(s);
-            e = nexttoken.value;
-            if (nexttoken.type !== '(identifier)') {
-                warning("Expected an identifier and instead saw '{a}'.",
-                    nexttoken, e);
-            } else {
-                addlabel(e, 'exception');
-            }
-            advance();
-            advance(')');
-            block(false);
-            b = true;
-            scope = s;
-        }
-        if (nexttoken.id === 'finally') {
-            advance('finally');
-            block(false);
-            return;
-        } else if (!b) {
-            error("Expected '{a}' and instead saw '{b}'.",
-                    nexttoken, 'catch', nexttoken.value);
-        }
-        return this;
-    });
-
-    blockstmt('while', function () {
-        var t = nexttoken;
-        funct['(breakage)'] += 1;
-        funct['(loopage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        expression(20);
-        if (nexttoken.id === '=') {
-            if (!option.boss)
-                warning("Expected a conditional expression and instead saw an assignment.");
-            advance('=');
-            expression(20);
-        }
-        advance(')', t);
-        nospace(prevtoken, token);
-        block(true, true);
-        funct['(breakage)'] -= 1;
-        funct['(loopage)'] -= 1;
-        return this;
-    }).labelled = true;
-
-    reserve('with');
-
-    blockstmt('switch', function () {
-        var t = nexttoken,
-            g = false;
-        funct['(breakage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        this.condition = expression(20);
-        advance(')', t);
-        nospace(prevtoken, token);
-        nonadjacent(token, nexttoken);
-        t = nexttoken;
-        advance('{');
-        nonadjacent(token, nexttoken);
-        indent += option.indent;
-        this.cases = [];
-        for (;;) {
-            switch (nexttoken.id) {
-            case 'case':
-                switch (funct['(verb)']) {
-                case 'break':
-                case 'case':
-                case 'continue':
-                case 'return':
-                case 'switch':
-                case 'throw':
-                    break;
-                default:
-                    // You can tell JSHint that you don't use break intentionally by
-                    // adding a comment /* falls through */ on a line just before
-                    // the next `case`.
-                    if (!ft.test(lines[nexttoken.line - 2])) {
-                        warning(
-                            "Expected a 'break' statement before 'case'.",
-                            token);
-                    }
-                }
-                indentation(-option.indent);
-                advance('case');
-                this.cases.push(expression(20));
-                g = true;
-                advance(':');
-                funct['(verb)'] = 'case';
-                break;
-            case 'default':
-                switch (funct['(verb)']) {
-                case 'break':
-                case 'continue':
-                case 'return':
-                case 'throw':
-                    break;
-                default:
-                    if (!ft.test(lines[nexttoken.line - 2])) {
-                        warning(
-                            "Expected a 'break' statement before 'default'.",
-                            token);
-                    }
-                }
-                indentation(-option.indent);
-                advance('default');
-                g = true;
-                advance(':');
-                break;
-            case '}':
-                indent -= option.indent;
-                indentation();
-                advance('}', t);
-                if (this.cases.length === 1 || this.condition.id === 'true' ||
-                        this.condition.id === 'false') {
-                    if (!option.onecase)
-                        warning("This 'switch' should be an 'if'.", this);
-                }
-                funct['(breakage)'] -= 1;
-                funct['(verb)'] = undefined;
-                return;
-            case '(end)':
-                error("Missing '{a}'.", nexttoken, '}');
-                return;
-            default:
-                if (g) {
-                    switch (token.id) {
-                    case ',':
-                        error("Each value should have its own case label.");
-                        return;
-                    case ':':
-                        g = false;
-                        statements();
-                        break;
-                    default:
-                        error("Missing ':' on a case clause.", token);
-                        return;
-                    }
-                } else {
-                    if (token.id === ':') {
-                        advance(':');
-                        error("Unexpected '{a}'.", token, ':');
-                        statements();
-                    } else {
-                        error("Expected '{a}' and instead saw '{b}'.",
-                            nexttoken, 'case', nexttoken.value);
-                        return;
-                    }
-                }
-            }
-        }
-    }).labelled = true;
-
-    stmt('debugger', function () {
-        if (!option.debug) {
-            warning("All 'debugger' statements should be removed.");
-        }
-        return this;
-    }).exps = true;
-
-    (function () {
-        var x = stmt('do', function () {
-            funct['(breakage)'] += 1;
-            funct['(loopage)'] += 1;
-            this.first = block(true);
-            advance('while');
-            var t = nexttoken;
-            nonadjacent(token, t);
-            advance('(');
-            nospace();
-            expression(20);
-            if (nexttoken.id === '=') {
-                if (!option.boss)
-                    warning("Expected a conditional expression and instead saw an assignment.");
-                advance('=');
-                expression(20);
-            }
-            advance(')', t);
-            nospace(prevtoken, token);
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        });
-        x.labelled = true;
-        x.exps = true;
-    }());
-
-    blockstmt('for', function () {
-        var s, t = nexttoken;
-        funct['(breakage)'] += 1;
-        funct['(loopage)'] += 1;
-        advance('(');
-        nonadjacent(this, t);
-        nospace();
-        if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') {
-            if (nexttoken.id === 'var') {
-                advance('var');
-                varstatement.fud.call(varstatement, true);
-            } else {
-                switch (funct[nexttoken.value]) {
-                case 'unused':
-                    funct[nexttoken.value] = 'var';
-                    break;
-                case 'var':
-                    break;
-                default:
-                    warning("Bad for in variable '{a}'.",
-                            nexttoken, nexttoken.value);
-                }
-                advance();
-            }
-            advance('in');
-            expression(20);
-            advance(')', t);
-            s = block(true, true);
-            if (option.forin && s && (s.length > 1 || typeof s[0] !== 'object' ||
-                    s[0].value !== 'if')) {
-                warning("The body of a for in should be wrapped in an if statement to filter " +
-                        "unwanted properties from the prototype.", this);
-            }
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        } else {
-            if (nexttoken.id !== ';') {
-                if (nexttoken.id === 'var') {
-                    advance('var');
-                    varstatement.fud.call(varstatement);
-                } else {
-                    for (;;) {
-                        expression(0, 'for');
-                        if (nexttoken.id !== ',') {
-                            break;
-                        }
-                        comma();
-                    }
-                }
-            }
-            nolinebreak(token);
-            advance(';');
-            if (nexttoken.id !== ';') {
-                expression(20);
-                if (nexttoken.id === '=') {
-                    if (!option.boss)
-                        warning("Expected a conditional expression and instead saw an assignment.");
-                    advance('=');
-                    expression(20);
-                }
-            }
-            nolinebreak(token);
-            advance(';');
-            if (nexttoken.id === ';') {
-                error("Expected '{a}' and instead saw '{b}'.",
-                        nexttoken, ')', ';');
-            }
-            if (nexttoken.id !== ')') {
-                for (;;) {
-                    expression(0, 'for');
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    comma();
-                }
-            }
-            advance(')', t);
-            nospace(prevtoken, token);
-            block(true, true);
-            funct['(breakage)'] -= 1;
-            funct['(loopage)'] -= 1;
-            return this;
-        }
-    }).labelled = true;
-
-
-    stmt('break', function () {
-        var v = nexttoken.value;
-
-        if (funct['(breakage)'] === 0)
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-
-        if (!option.asi)
-            nolinebreak(this);
-
-        if (nexttoken.id !== ';') {
-            if (token.line === nexttoken.line) {
-                if (funct[v] !== 'label') {
-                    warning("'{a}' is not a statement label.", nexttoken, v);
-                } else if (scope[v] !== funct) {
-                    warning("'{a}' is out of scope.", nexttoken, v);
-                }
-                this.first = nexttoken;
-                advance();
-            }
-        }
-        reachable('break');
-        return this;
-    }).exps = true;
-
-
-    stmt('continue', function () {
-        var v = nexttoken.value;
-
-        if (funct['(breakage)'] === 0)
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-
-        if (!option.asi)
-            nolinebreak(this);
-
-        if (nexttoken.id !== ';') {
-            if (token.line === nexttoken.line) {
-                if (funct[v] !== 'label') {
-                    warning("'{a}' is not a statement label.", nexttoken, v);
-                } else if (scope[v] !== funct) {
-                    warning("'{a}' is out of scope.", nexttoken, v);
-                }
-                this.first = nexttoken;
-                advance();
-            }
-        } else if (!funct['(loopage)']) {
-            warning("Unexpected '{a}'.", nexttoken, this.value);
-        }
-        reachable('continue');
-        return this;
-    }).exps = true;
-
-
-    stmt('return', function () {
-        if (this.line === nexttoken.line) {
-            if (nexttoken.id === '(regexp)')
-                warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator.");
-
-            if (nexttoken.id !== ';' && !nexttoken.reach) {
-                nonadjacent(token, nexttoken);
-                if (peek().value === "=" && !option.boss) {
-                    warningAt("Did you mean to return a conditional instead of an assignment?",
-                              token.line, token.character + 1);
-                }
-                this.first = expression(0);
-            }
-        } else if (!option.asi) {
-            nolinebreak(this); // always warn (Line breaking error)
-        }
-        reachable('return');
-        return this;
-    }).exps = true;
-
-
-    stmt('throw', function () {
-        nolinebreak(this);
-        nonadjacent(token, nexttoken);
-        this.first = expression(20);
-        reachable('throw');
-        return this;
-    }).exps = true;
-
-//  Superfluous reserved words
-
-    reserve('class');
-    reserve('const');
-    reserve('enum');
-    reserve('export');
-    reserve('extends');
-    reserve('import');
-    reserve('super');
-
-    reserve('let');
-    reserve('yield');
-    reserve('implements');
-    reserve('interface');
-    reserve('package');
-    reserve('private');
-    reserve('protected');
-    reserve('public');
-    reserve('static');
-
-
-// Parse JSON
-
-    function jsonValue() {
-
-        function jsonObject() {
-            var o = {}, t = nexttoken;
-            advance('{');
-            if (nexttoken.id !== '}') {
-                for (;;) {
-                    if (nexttoken.id === '(end)') {
-                        error("Missing '}' to match '{' from line {a}.",
-                                nexttoken, t.line);
-                    } else if (nexttoken.id === '}') {
-                        warning("Unexpected comma.", token);
-                        break;
-                    } else if (nexttoken.id === ',') {
-                        error("Unexpected comma.", nexttoken);
-                    } else if (nexttoken.id !== '(string)') {
-                        warning("Expected a string and instead saw {a}.",
-                                nexttoken, nexttoken.value);
-                    }
-                    if (o[nexttoken.value] === true) {
-                        warning("Duplicate key '{a}'.",
-                                nexttoken, nexttoken.value);
-                    } else if ((nexttoken.value === '__proto__' &&
-                        !option.proto) || (nexttoken.value === '__iterator__' &&
-                        !option.iterator)) {
-                        warning("The '{a}' key may produce unexpected results.",
-                            nexttoken, nexttoken.value);
-                    } else {
-                        o[nexttoken.value] = true;
-                    }
-                    advance();
-                    advance(':');
-                    jsonValue();
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    advance(',');
-                }
-            }
-            advance('}');
-        }
-
-        function jsonArray() {
-            var t = nexttoken;
-            advance('[');
-            if (nexttoken.id !== ']') {
-                for (;;) {
-                    if (nexttoken.id === '(end)') {
-                        error("Missing ']' to match '[' from line {a}.",
-                                nexttoken, t.line);
-                    } else if (nexttoken.id === ']') {
-                        warning("Unexpected comma.", token);
-                        break;
-                    } else if (nexttoken.id === ',') {
-                        error("Unexpected comma.", nexttoken);
-                    }
-                    jsonValue();
-                    if (nexttoken.id !== ',') {
-                        break;
-                    }
-                    advance(',');
-                }
-            }
-            advance(']');
-        }
-
-        switch (nexttoken.id) {
-        case '{':
-            jsonObject();
-            break;
-        case '[':
-            jsonArray();
-            break;
-        case 'true':
-        case 'false':
-        case 'null':
-        case '(number)':
-        case '(string)':
-            advance();
-            break;
-        case '-':
-            advance('-');
-            if (token.character !== nexttoken.from) {
-                warning("Unexpected space after '-'.", token);
-            }
-            adjacent(token, nexttoken);
-            advance('(number)');
-            break;
-        default:
-            error("Expected a JSON value.", nexttoken);
-        }
-    }
-
-
-// The actual JSHINT function itself.
-
-    var itself = function (s, o, g) {
-        var a, i, k;
-        JSHINT.errors = [];
-        JSHINT.undefs = [];
-        predefined = Object.create(standard);
-        combine(predefined, g || {});
-        if (o) {
-            a = o.predef;
-            if (a) {
-                if (Array.isArray(a)) {
-                    for (i = 0; i < a.length; i += 1) {
-                        predefined[a[i]] = true;
-                    }
-                } else if (typeof a === 'object') {
-                    k = Object.keys(a);
-                    for (i = 0; i < k.length; i += 1) {
-                        predefined[k[i]] = !!a[k[i]];
-                    }
-                }
-            }
-            option = o;
-        } else {
-            option = {};
-        }
-        option.indent = option.indent || 4;
-        option.maxerr = option.maxerr || 50;
-
-        tab = '';
-        for (i = 0; i < option.indent; i += 1) {
-            tab += ' ';
-        }
-        indent = 1;
-        global = Object.create(predefined);
-        scope = global;
-        funct = {
-            '(global)': true,
-            '(name)': '(global)',
-            '(scope)': scope,
-            '(breakage)': 0,
-            '(loopage)': 0
-        };
-        functions = [funct];
-        urls = [];
-        stack = null;
-        member = {};
-        membersOnly = null;
-        implied = {};
-        inblock = false;
-        lookahead = [];
-        jsonmode = false;
-        warnings = 0;
-        lex.init(s);
-        prereg = true;
-        directive = {};
-
-        prevtoken = token = nexttoken = syntax['(begin)'];
-        assume();
-
-        // combine the passed globals after we've assumed all our options
-        combine(predefined, g || {});
-
-        try {
-            advance();
-            switch (nexttoken.id) {
-            case '{':
-            case '[':
-                option.laxbreak = true;
-                jsonmode = true;
-                jsonValue();
-                break;
-            default:
-                directives();
-                if (directive["use strict"] && !option.globalstrict) {
-                    warning("Use the function form of \"use strict\".", prevtoken);
-                }
-
-                statements();
-            }
-            advance('(end)');
-        } catch (e) {
-            if (e) {
-                var nt = nexttoken || {};
-                JSHINT.errors.push({
-                    raw       : e.raw,
-                    reason    : e.message,
-                    line      : e.line || nt.line,
-                    character : e.character || nt.from
-                }, null);
-            }
-        }
-
-        for (i = 0; i < JSHINT.undefs.length; i += 1) {
-            k = JSHINT.undefs[i].slice(0);
-            scope = k.shift();
-            a = k[2];
-
-            if (typeof scope[a] !== 'string' && typeof funct[a] !== 'string') {
-                warning.apply(warning, k);
-            }
-        }
-
-        return JSHINT.errors.length === 0;
-    };
-
-    // Data summary.
-    itself.data = function () {
-
-        var data = { functions: [], options: option }, fu, globals, implieds = [], f, i, j,
-            members = [], n, unused = [], v;
-        if (itself.errors.length) {
-            data.errors = itself.errors;
-        }
-
-        if (jsonmode) {
-            data.json = true;
-        }
-
-        for (n in implied) {
-            if (is_own(implied, n)) {
-                implieds.push({
-                    name: n,
-                    line: implied[n]
-                });
-            }
-        }
-        if (implieds.length > 0) {
-            data.implieds = implieds;
-        }
-
-        if (urls.length > 0) {
-            data.urls = urls;
-        }
-
-        globals = Object.keys(scope);
-        if (globals.length > 0) {
-            data.globals = globals;
-        }
-
-        for (i = 1; i < functions.length; i += 1) {
-            f = functions[i];
-            fu = {};
-            for (j = 0; j < functionicity.length; j += 1) {
-                fu[functionicity[j]] = [];
-            }
-            for (n in f) {
-                if (is_own(f, n) && n.charAt(0) !== '(') {
-                    v = f[n];
-                    if (v === 'unction') {
-                        v = 'unused';
-                    }
-                    if (Array.isArray(fu[v])) {
-                        fu[v].push(n);
-                        if (v === 'unused') {
-                            unused.push({
-                                name: n,
-                                line: f['(line)'],
-                                'function': f['(name)']
-                            });
-                        }
-                    }
-                }
-            }
-            for (j = 0; j < functionicity.length; j += 1) {
-                if (fu[functionicity[j]].length === 0) {
-                    delete fu[functionicity[j]];
-                }
-            }
-            fu.name = f['(name)'];
-            fu.param = f['(params)'];
-            fu.line = f['(line)'];
-            fu.last = f['(last)'];
-            data.functions.push(fu);
-        }
-
-        if (unused.length > 0) {
-            data.unused = unused;
-        }
-
-        members = [];
-        for (n in member) {
-            if (typeof member[n] === 'number') {
-                data.member = member;
-                break;
-            }
-        }
-
-        return data;
-    };
-
-    itself.report = function (option) {
-        var data = itself.data();
-
-        var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s;
-
-        function detail(h, array) {
-            var b, i, singularity;
-            if (array) {
-                o.push('<div><i>' + h + '</i> ');
-                array = array.sort();
-                for (i = 0; i < array.length; i += 1) {
-                    if (array[i] !== singularity) {
-                        singularity = array[i];
-                        o.push((b ? ', ' : '') + singularity);
-                        b = true;
-                    }
-                }
-                o.push('</div>');
-            }
-        }
-
-
-        if (data.errors || data.implieds || data.unused) {
-            err = true;
-            o.push('<div id=errors><i>Error:</i>');
-            if (data.errors) {
-                for (i = 0; i < data.errors.length; i += 1) {
-                    c = data.errors[i];
-                    if (c) {
-                        e = c.evidence || '';
-                        o.push('<p>Problem' + (isFinite(c.line) ? ' at line ' +
-                                c.line + ' character ' + c.character : '') +
-                                ': ' + c.reason.entityify() +
-                                '</p><p class=evidence>' +
-                                (e && (e.length > 80 ? e.slice(0, 77) + '...' :
-                                e).entityify()) + '</p>');
-                    }
-                }
-            }
-
-            if (data.implieds) {
-                s = [];
-                for (i = 0; i < data.implieds.length; i += 1) {
-                    s[i] = '<code>' + data.implieds[i].name + '</code>&nbsp;<i>' +
-                        data.implieds[i].line + '</i>';
-                }
-                o.push('<p><i>Implied global:</i> ' + s.join(', ') + '</p>');
-            }
-
-            if (data.unused) {
-                s = [];
-                for (i = 0; i < data.unused.length; i += 1) {
-                    s[i] = '<code><u>' + data.unused[i].name + '</u></code>&nbsp;<i>' +
-                        data.unused[i].line + '</i> <code>' +
-                        data.unused[i]['function'] + '</code>';
-                }
-                o.push('<p><i>Unused variable:</i> ' + s.join(', ') + '</p>');
-            }
-            if (data.json) {
-                o.push('<p>JSON: bad.</p>');
-            }
-            o.push('</div>');
-        }
-
-        if (!option) {
-
-            o.push('<br><div id=functions>');
-
-            if (data.urls) {
-                detail("URLs<br>", data.urls, '<br>');
-            }
-
-            if (data.json && !err) {
-                o.push('<p>JSON: good.</p>');
-            } else if (data.globals) {
-                o.push('<div><i>Global</i> ' +
-                        data.globals.sort().join(', ') + '</div>');
-            } else {
-                o.push('<div><i>No new global variables introduced.</i></div>');
-            }
-
-            for (i = 0; i < data.functions.length; i += 1) {
-                f = data.functions[i];
-
-                o.push('<br><div class=function><i>' + f.line + '-' +
-                        f.last + '</i> ' + (f.name || '') + '(' +
-                        (f.param ? f.param.join(', ') : '') + ')</div>');
-                detail('<big><b>Unused</b></big>', f.unused);
-                detail('Closure', f.closure);
-                detail('Variable', f['var']);
-                detail('Exception', f.exception);
-                detail('Outer', f.outer);
-                detail('Global', f.global);
-                detail('Label', f.label);
-            }
-
-            if (data.member) {
-                a = Object.keys(data.member);
-                if (a.length) {
-                    a = a.sort();
-                    m = '<br><pre id=members>/*members ';
-                    l = 10;
-                    for (i = 0; i < a.length; i += 1) {
-                        k = a[i];
-                        n = k.name();
-                        if (l + n.length > 72) {
-                            o.push(m + '<br>');
-                            m = '    ';
-                            l = 1;
-                        }
-                        l += n.length + 2;
-                        if (data.member[k] === 1) {
-                            n = '<i>' + n + '</i>';
-                        }
-                        if (i < a.length - 1) {
-                            n += ', ';
-                        }
-                        m += n;
-                    }
-                    o.push(m + '<br>*/</pre>');
-                }
-                o.push('</div>');
-            }
-        }
-        return o.join('');
-    };
-
-    itself.jshint = itself;
-    itself.edition = '2011-04-16';
-
-    return itself;
-}());
-
-// Make JSHINT a Node module, if possible.
-if (typeof exports === 'object' && exports)
-    exports.JSHINT = JSHINT;
diff --git a/lint.sh b/lint.sh
deleted file mode 100755 (executable)
index cd0db4c..0000000
--- a/lint.sh
+++ /dev/null
@@ -1,47 +0,0 @@
-#!/bin/bash
-#
-# Usage:
-#   ./lint.sh [file.js]
-#
-# The zero-argument form lints everything.
-
-# See jshint/build/jshint-rhino.js for documentation on these parameters.
-# devel   defines logging globals (i.e. "console.log")
-# browser defines standard web browser globals (i.e. "document")
-# shadow  disables warnings on multiple var definitions in one scope (i.e. two
-#         loops with "var i")
-jsc_opts='maxerr:10000,devel:true,browser:true,shadow:true'
-rhino_opts='maxerr=10000,devel=true,browser=true,shadow=true'
-
-RETURN_VALUE=0
-
-if [ $# -eq 0 ]; then
-  files=$(ls dygraph*.js plugins/*.js datahandler/*.js gallery/*.js | grep -v combined | grep -v dev.js| grep -v externs)
-else
-  files=$@
-fi
-
-if [ -e /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Resources/jsc ]; then
-  # use JSC (Safari/JavaScriptCore) to run JSHint -- much faster than Rhino.
-  echo 'Running JSHint w/ JavaScriptCore (jsc)...'
-  for file in $files; do
-    ./jshint/env/jsc.sh $file $jsc_opts || RETURN_VALUE=1
-  done
-else
-  # fall back to Rhino.
-  echo 'Running JSHint w/ Rhino...'
-
-  for FILE in $files; do
-    LINT_RESULT=$(java -jar ./jsdoc-toolkit/java/classes/js.jar ./jshint/build/jshint-rhino.js $rhino_opts $FILE)
-    ERRORS=$(echo ${LINT_RESULT} | egrep [^\s] -c)
-    if [[ ${ERRORS} -ne 0 ]]; then
-      echo "[jshint] Error(s) in ${FILE}:"
-      printf "%s\n" "${LINT_RESULT}"
-      RETURN_VALUE=1
-    else
-      echo "[jshint] ${FILE} passed!"
-    fi
-  done
-fi
-
-exit $RETURN_VALUE
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..5bd9c98
--- /dev/null
@@ -0,0 +1,38 @@
+{
+  "name": "dygraphs",
+  "version": "1.0.1",
+  "description": "dygraphs is a fast, flexible open source JavaScript charting library.",
+  "main": "dygraph.js",
+  "directories": {
+    "doc": "docs",
+    "test": "tests"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/danvk/dygraphs.git"
+  },
+  "keywords": [
+    "dygraphs",
+    "javascript",
+    "visualization",
+    "canvas",
+    "chart",
+    "timeseries"
+  ],
+  "author": "Dan Vanderkam (danvdk@gmail.com)",
+  "license": "MIT",
+  "bugs": {
+    "url": "https://github.com/danvk/dygraphs/issues"
+  },
+  "homepage": "https://github.com/danvk/dygraphs",
+  "devDependencies": {
+    "closure-compiler": "^0.2.6",
+    "jshint": "^2.5.10",
+    "obvious-closure-library": "^20140401.0.2",
+    "phantomjs": "^1.9.7-8",
+    "uglify-js": "^2"
+  },
+  "scripts": {
+    "test": "make test"
+  }
+}
index 22f4687..c534964 100644 (file)
@@ -63,9 +63,6 @@ page.open(url, function(status) {
   results = page.evaluate(function() {
     var num_passing = 0, num_failing = 0;
     var failures = [];
-    // Phantom doesn't like stacktrace.js using the "arguments" object
-    // in stacktrace.js, which it interprets in strict mode.
-    printStackTrace = undefined;
 
     jstestdriver.attachListener({
       finish : function(tc, name, result, e) {
index 6623a0b..239d36c 100644 (file)
@@ -112,8 +112,8 @@ axes.prototype.willDrawChart = function(e) {
 
   var context = e.drawingContext;
   var containerDiv = e.canvas.parentNode;
-  var canvasWidth = e.canvas.width;
-  var canvasHeight = e.canvas.height;
+  var canvasWidth = g.width_;  // e.canvas.width is affected by pixel ratio.
+  var canvasHeight = g.height_;
 
   var label, x, y, tick, i;
 
index 9954682..a5b434c 100644 (file)
@@ -7,17 +7,14 @@
 
 Dygraph.Plugins.Legend = (function() {
 /*
-
 Current bits of jankiness:
 - Uses two private APIs:
     1. Dygraph.optionsViewForAxis_
     2. dygraph.plotter_.area
 - Registers for a "predraw" event, which should be renamed.
 - I call calculateEmWidthInDiv more often than needed.
-
 */
 
-/*jshint globalstrict: true */
 /*global Dygraph:false */
 "use strict";
 
@@ -38,7 +35,7 @@ legend.prototype.toString = function() {
 };
 
 // (defined below)
-var generateLegendHTML, generateLegendDashHTML;
+var generateLegendDashHTML;
 
 /**
  * This is called during the dygraph constructor, after options have been set
@@ -88,7 +85,7 @@ legend.prototype.activate = function(g) {
       try {
         div.style[name] = messagestyle[name];
       } catch (e) {
-        Dygraph.warn("You are using unsupported css properties for your " +
+        console.warn("You are using unsupported css properties for your " +
             "browser in labelsDivStyles");
       }
     }
@@ -153,6 +150,7 @@ legend.prototype.select = function(e) {
   }
 
   var html = generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_);
+  var html = legend.generateLegendHTML(e.dygraph, xValue, points, this.one_em_width_);
   this.legend_div_.innerHTML = html;
 };
 
@@ -166,7 +164,7 @@ legend.prototype.deselect = function(e) {
   var oneEmWidth = calculateEmWidthInDiv(this.legend_div_);
   this.one_em_width_ = oneEmWidth;
 
-  var html = generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth);
+  var html = legend.generateLegendHTML(e.dygraph, undefined, undefined, oneEmWidth);
   this.legend_div_.innerHTML = html;
 };
 
@@ -216,7 +214,7 @@ legend.prototype.destroy = function() {
  * relevant when displaying a legend with no selection (i.e. {legend:
  * 'always'}) and with dashed lines.
  */
-generateLegendHTML = function(g, x, sel_points, oneEmWidth) {
+legend.generateLegendHTML = function(g, x, sel_points, oneEmWidth) {
   // TODO(danvk): deprecate this option in place of {legend: 'never'}
   if (g.getOption('showLabelsOnHighlight') !== true) return '';
 
@@ -278,7 +276,7 @@ generateLegendHTML = function(g, x, sel_points, oneEmWidth) {
 
     // TODO(danvk): use a template string here and make it an attribute.
     html += "<span" + cls + ">" + " <b><span style='color: " + series.color + ";'>" +
-        escapeHTML(pt.name) + "</span></b>:&nbsp;" + yval + "</span>";
+        escapeHTML(pt.name) + "</span></b>:&#160;" + yval + "</span>";
   }
   return html;
 };
index fe32b1a..399ed39 100644 (file)
@@ -12,7 +12,6 @@
 
 Dygraph.Plugins.RangeSelector = (function() {
 
-/*jshint globalstrict: true */
 /*global Dygraph:false */
 "use strict";
 
@@ -52,12 +51,12 @@ rangeSelector.prototype.destroy = function() {
 // Private methods
 //------------------------------------------------------------------
 
-rangeSelector.prototype.getOption_ = function(name) {
-  return this.dygraph_.getOption(name);
+rangeSelector.prototype.getOption_ = function(name, opt_series) {
+  return this.dygraph_.getOption(name, opt_series);
 };
 
 rangeSelector.prototype.setDefaultOption_ = function(name, value) {
-  return this.dygraph_.attrs_[name] = value;
+  this.dygraph_.attrs_[name] = value;
 };
 
 /**
@@ -74,7 +73,7 @@ rangeSelector.prototype.createInterface_ = function() {
 
   // Range selector and animatedZooms have a bad interaction. See issue 359.
   if (this.getOption_('animatedZooms')) {
-    Dygraph.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
+    console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
     this.dygraph_.updateOptions({animatedZooms: false}, true);
   }
 
@@ -166,13 +165,19 @@ rangeSelector.prototype.updateVisibility_ = function() {
  * Resizes the range selector.
  */
 rangeSelector.prototype.resize_ = function() {
-  function setElementRect(canvas, rect) {
+  function setElementRect(canvas, context, rect) {
+    var canvasScale = Dygraph.getContextPixelRatio(context);
+
     canvas.style.top = rect.y + 'px';
     canvas.style.left = rect.x + 'px';
-    canvas.width = rect.w;
-    canvas.height = rect.h;
-    canvas.style.width = canvas.width + 'px';    // for IE
-    canvas.style.height = canvas.height + 'px';  // for IE
+    canvas.width = rect.w * canvasScale;
+    canvas.height = rect.h * canvasScale;
+    canvas.style.width = rect.w + 'px';
+    canvas.style.height = rect.h + 'px';
+
+    if(canvasScale != 1) {
+      context.scale(canvasScale, canvasScale);
+    }
   }
 
   var plotArea = this.dygraph_.layout_.getPlotArea();
@@ -188,8 +193,8 @@ rangeSelector.prototype.resize_ = function() {
     h: this.getOption_('rangeSelectorHeight')
   };
 
-  setElementRect(this.bgcanvas_, this.canvasRect_);
-  setElementRect(this.fgcanvas_, this.canvasRect_);
+  setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_);
+  setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_);
 };
 
 /**
@@ -544,7 +549,7 @@ rangeSelector.prototype.drawStaticLayer_ = function() {
   try {
     this.drawMiniPlot_();
   } catch(ex) {
-    Dygraph.warn(ex);
+    console.warn(ex);
   }
 
   var margin = 0.5;
@@ -594,6 +599,13 @@ rangeSelector.prototype.drawMiniPlot_ = function() {
     var dataPoint = combinedSeriesData.data[i];
     var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN);
     var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN);
+
+    // Skip points that don't change the x-value. Overly fine-grained points
+    // can cause major slowdowns with the ctx.fill() call below.
+    if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) {
+      continue;
+    }
+
     if (isFinite(x) && isFinite(y)) {
       if(prevX === null) {
         ctx.lineTo(x, canvasHeight);
@@ -646,15 +658,29 @@ rangeSelector.prototype.drawMiniPlot_ = function() {
 rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
   var g = this.dygraph_;
   var logscale = this.getOption_('logscale');
-
-  // Create a combined series (average of all series values).
   var i;
 
+  // Select series to combine. By default, all series are combined.
+  var numColumns = g.numColumns();
+  var labels = g.getLabels();
+  var includeSeries = new Array(numColumns);
+  var anySet = false;
+  for (i = 1; i < numColumns; i++) {
+    var include = this.getOption_('showInRangeSelector', labels[i]);
+    includeSeries[i] = include;
+    if (include !== null) anySet = true;  // it's set explicitly for this series
+  }
+  if (!anySet) {
+    for (i = 0; i < includeSeries.length; i++) includeSeries[i] = true;
+  }
+
+  // Create a combined series (average of selected series values).
   // TODO(danvk): short-circuit if there's only one series.
   var rolledSeries = [];
   var dataHandler = g.dataHandler_;
   var options = g.attributes_;
   for (i = 1; i < g.numColumns(); i++) {
+    if (!includeSeries[i]) continue;
     var series = dataHandler.extractSeries(g.rawData_, i, options);
     if (g.rollPeriod() > 1) {
       series = dataHandler.rollingAverage(series, g.rollPeriod(), options);
diff --git a/polyfills/console.js b/polyfills/console.js
new file mode 100644 (file)
index 0000000..4a6f542
--- /dev/null
@@ -0,0 +1,15 @@
+// Console-polyfill. MIT license.
+// https://github.com/paulmillr/console-polyfill
+// Make it safe to do console.log() always.
+(function(con) {
+  'use strict';
+  var prop, method;
+  var empty = {};
+  var dummy = function() {};
+  var properties = 'memory'.split(',');
+  var methods = ('assert,clear,count,debug,dir,dirxml,error,exception,group,' +
+     'groupCollapsed,groupEnd,info,log,markTimeline,profile,profiles,profileEnd,' +
+     'show,table,time,timeEnd,timeline,timelineEnd,timeStamp,trace,warn').split(',');
+  while (prop = properties.pop()) con[prop] = con[prop] || empty;
+  while (method = methods.pop()) con[method] = con[method] || dummy;
+})(this.console = this.console || {}); // Using `this` for web workers.
index 232790e..9b134a2 100755 (executable)
@@ -30,15 +30,15 @@ if [ -s docs/options.html ] ; then
   find . -path ./.git -prune -o -print | xargs chmod a+rX
 
   # Copy everything to the site.
-  rsync -avzr gallery common tests jsdoc experimental plugins datahandler $site \
+  rsync -avzr gallery common tests jsdoc experimental plugins datahandler polyfills extras $site \
   && \
-  rsync -avzr --copy-links dashed-canvas.js stacktrace.js dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png $temp_dir/* $site/
+  rsync -avzr --copy-links dashed-canvas.js dygraph*.js gadget.xml excanvas.js thumbnail.png screenshot.png $temp_dir/* $site/
 else
   echo "generate-documentation.py failed"
 fi
 
 # Revert changes to dygraph-combined.js and docs.
-git checkout dygraph-combined.js
+make clean-combined-test
 git checkout docs/download.html
 rm docs/options.html
 rm -rf $temp_dir
index 01089e4..909e109 100755 (executable)
@@ -26,6 +26,18 @@ if [ $? -ne 0 ]; then
   exit 1
 fi
 
+grep "$VERSION" package.json
+if [ $? -ne 0 ]; then
+  echo "Version in package.json doesn't match command line argument." >&2
+  exit 1
+fi
+
+grep "v$VERSION" bower.json
+if [ $? -ne 0 ]; then
+  echo "Version in bower.json doesn't match command line argument." >&2
+  exit 1
+fi
+
 make lint test test-combined
 if [ $? -ne 0 ]; then
   echo "Tests failed. Won't release!" >&2
diff --git a/stacktrace.js b/stacktrace.js
deleted file mode 100644 (file)
index 4d0384d..0000000
+++ /dev/null
@@ -1,411 +0,0 @@
-// Domain Public by Eric Wendelin http://eriwen.com/ (2008)
-//                  Luke Smith http://lucassmith.name/ (2008)
-//                  Loic Dachary <loic@dachary.org> (2008)
-//                  Johan Euphrosine <proppy@aminche.com> (2008)
-//                  Oyvind Sean Kinsey http://kinsey.no/blog (2010)
-//                  Victor Homyakov <victor-homyakov@users.sourceforge.net> (2010)
-
-/**
- * Main function giving a function stack trace with a forced or passed in Error
- *
- * @cfg {Error} e The error to create a stacktrace from (optional)
- * @cfg {Boolean} guess If we should try to resolve the names of anonymous functions
- * @return {Array} of Strings with functions, lines, files, and arguments where possible
- */
-function printStackTrace(options) {
-    options = options || {guess: true};
-    var ex = options.e || null, guess = !!options.guess;
-    var p = new printStackTrace.implementation(), result = p.run(ex);
-    return (guess) ? p.guessAnonymousFunctions(result) : result;
-}
-
-printStackTrace.implementation = function() {
-};
-
-printStackTrace.implementation.prototype = {
-    /**
-     * @param {Error} ex The error to create a stacktrace from (optional)
-     * @param {String} mode Forced mode (optional, mostly for unit tests)
-     */
-    run: function(ex, mode) {
-        ex = ex || this.createException();
-        // examine exception properties w/o debugger
-        //for (var prop in ex) {alert("Ex['" + prop + "']=" + ex[prop]);}
-        mode = mode || this.mode(ex);
-        if (mode === 'other') {
-            return this.other(arguments.callee);
-        } else {
-            return this[mode](ex);
-        }
-    },
-
-    createException: function() {
-        try {
-            this.undef();
-        } catch (e) {
-            return e;
-        }
-    },
-
-    /**
-     * Mode could differ for different exception, e.g.
-     * exceptions in Chrome may or may not have arguments or stack.
-     *
-     * @return {String} mode of operation for the exception
-     */
-    mode: function(e) {
-        if (e['arguments'] && e.stack) {
-            return 'chrome';
-        } else if (typeof e.message === 'string' && typeof window !== 'undefined' && window.opera) {
-            // e.message.indexOf("Backtrace:") > -1 -> opera
-            // !e.stacktrace -> opera
-            if (!e.stacktrace) {
-                return 'opera9'; // use e.message
-            }
-            // 'opera#sourceloc' in e -> opera9, opera10a
-            if (e.message.indexOf('\n') > -1 && e.message.split('\n').length > e.stacktrace.split('\n').length) {
-                return 'opera9'; // use e.message
-            }
-            // e.stacktrace && !e.stack -> opera10a
-            if (!e.stack) {
-                return 'opera10a'; // use e.stacktrace
-            }
-            // e.stacktrace && e.stack -> opera10b
-            if (e.stacktrace.indexOf("called from line") < 0) {
-                return 'opera10b'; // use e.stacktrace, format differs from 'opera10a'
-            }
-            // e.stacktrace && e.stack -> opera11
-            return 'opera11'; // use e.stacktrace, format differs from 'opera10a', 'opera10b'
-        } else if (e.stack) {
-            return 'firefox';
-        }
-        return 'other';
-    },
-
-    /**
-     * Given a context, function name, and callback function, overwrite it so that it calls
-     * printStackTrace() first with a callback and then runs the rest of the body.
-     *
-     * @param {Object} context of execution (e.g. window)
-     * @param {String} functionName to instrument
-     * @param {Function} function to call with a stack trace on invocation
-     */
-    instrumentFunction: function(context, functionName, callback) {
-        context = context || window;
-        var original = context[functionName];
-        context[functionName] = function instrumented() {
-            callback.call(this, printStackTrace().slice(4));
-            return context[functionName]._instrumented.apply(this, arguments);
-        };
-        context[functionName]._instrumented = original;
-    },
-
-    /**
-     * Given a context and function name of a function that has been
-     * instrumented, revert the function to it's original (non-instrumented)
-     * state.
-     *
-     * @param {Object} context of execution (e.g. window)
-     * @param {String} functionName to de-instrument
-     */
-    deinstrumentFunction: function(context, functionName) {
-        if (context[functionName].constructor === Function &&
-                context[functionName]._instrumented &&
-                context[functionName]._instrumented.constructor === Function) {
-            context[functionName] = context[functionName]._instrumented;
-        }
-    },
-
-    /**
-     * Given an Error object, return a formatted Array based on Chrome's stack string.
-     *
-     * @param e - Error object to inspect
-     * @return Array<String> of function calls, files and line numbers
-     */
-    chrome: function(e) {
-        var stack = (e.stack + '\n').replace(/^\S[^\(]+?[\n$]/gm, '').
-          replace(/^\s+at\s+/gm, '').
-          replace(/^([^\(]+?)([\n$])/gm, '{anonymous}()@$1$2').
-          replace(/^Object.<anonymous>\s*\(([^\)]+)\)/gm, '{anonymous}()@$1').split('\n');
-        stack.pop();
-        return stack;
-    },
-
-    /**
-     * Given an Error object, return a formatted Array based on Firefox's stack string.
-     *
-     * @param e - Error object to inspect
-     * @return Array<String> of function calls, files and line numbers
-     */
-    firefox: function(e) {
-        return e.stack.replace(/(?:\n@:0)?\s+$/m, '').replace(/^\(/gm, '{anonymous}(').split('\n');
-    },
-
-    opera11: function(e) {
-        // "Error thrown at line 42, column 12 in <anonymous function>() in file://localhost/G:/js/stacktrace.js:\n"
-        // "Error thrown at line 42, column 12 in <anonymous function: createException>() in file://localhost/G:/js/stacktrace.js:\n"
-        // "called from line 7, column 4 in bar(n) in file://localhost/G:/js/test/functional/testcase1.html:\n"
-        // "called from line 15, column 3 in file://localhost/G:/js/test/functional/testcase1.html:\n"
-        var ANON = '{anonymous}', lineRE = /^.*line (\d+), column (\d+)(?: in (.+))? in (\S+):$/;
-        var lines = e.stacktrace.split('\n'), result = [];
-
-        for (var i = 0, len = lines.length; i < len; i += 2) {
-            var match = lineRE.exec(lines[i]);
-            if (match) {
-                var location = match[4] + ':' + match[1] + ':' + match[2];
-                var fnName = match[3] || "global code";
-                fnName = fnName.replace(/<anonymous function: (\S+)>/, "$1").replace(/<anonymous function>/, ANON);
-                result.push(fnName + '@' + location + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
-            }
-        }
-
-        return result;
-    },
-
-    opera10b: function(e) {
-        // "<anonymous function: run>([arguments not available])@file://localhost/G:/js/stacktrace.js:27\n" +
-        // "printStackTrace([arguments not available])@file://localhost/G:/js/stacktrace.js:18\n" +
-        // "@file://localhost/G:/js/test/functional/testcase1.html:15"
-        var ANON = '{anonymous}', lineRE = /^(.*)@(.+):(\d+)$/;
-        var lines = e.stacktrace.split('\n'), result = [];
-
-        for (var i = 0, len = lines.length; i < len; i++) {
-            var match = lineRE.exec(lines[i]);
-            if (match) {
-                var fnName = match[1]? (match[1] + '()') : "global code";
-                result.push(fnName + '@' + match[2] + ':' + match[3]);
-            }
-        }
-
-        return result;
-    },
-
-    /**
-     * Given an Error object, return a formatted Array based on Opera 10's stacktrace string.
-     *
-     * @param e - Error object to inspect
-     * @return Array<String> of function calls, files and line numbers
-     */
-    opera10a: function(e) {
-        // "  Line 27 of linked script file://localhost/G:/js/stacktrace.js\n"
-        // "  Line 11 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html: In function foo\n"
-        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
-        var lines = e.stacktrace.split('\n'), result = [];
-
-        for (var i = 0, len = lines.length; i < len; i += 2) {
-            var match = lineRE.exec(lines[i]);
-            if (match) {
-                var fnName = match[3] || ANON;
-                result.push(fnName + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
-            }
-        }
-
-        return result;
-    },
-
-    // Opera 7.x-9.2x only!
-    opera9: function(e) {
-        // "  Line 43 of linked script file://localhost/G:/js/stacktrace.js\n"
-        // "  Line 7 of inline#1 script in file://localhost/G:/js/test/functional/testcase1.html\n"
-        var ANON = '{anonymous}', lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
-        var lines = e.message.split('\n'), result = [];
-
-        for (var i = 2, len = lines.length; i < len; i += 2) {
-            var match = lineRE.exec(lines[i]);
-            if (match) {
-                result.push(ANON + '()@' + match[2] + ':' + match[1] + ' -- ' + lines[i + 1].replace(/^\s+/, ''));
-            }
-        }
-
-        return result;
-    },
-
-    // Safari, IE, and others
-    other: function(curr) {
-        var ANON = '{anonymous}', fnRE = /function\s*([\w\-$]+)?\s*\(/i, stack = [], fn, args, maxStackSize = 10;
-        while (curr && stack.length < maxStackSize) {
-            fn = fnRE.test(curr.toString()) ? RegExp.$1 || ANON : ANON;
-            args = Array.prototype.slice.call(curr['arguments'] || []);
-            stack[stack.length] = fn + '(' + this.stringifyArguments(args) + ')';
-            curr = curr.caller;
-        }
-        return stack;
-    },
-
-    /**
-     * Given arguments array as a String, subsituting type names for non-string types.
-     *
-     * @param {Arguments} object
-     * @return {Array} of Strings with stringified arguments
-     */
-    stringifyArguments: function(args) {
-        var result = [];
-        var slice = Array.prototype.slice;
-        for (var i = 0; i < args.length; ++i) {
-            var arg = args[i];
-            if (arg === undefined) {
-                result[i] = 'undefined';
-            } else if (arg === null) {
-                result[i] = 'null';
-            } else if (arg.constructor) {
-                if (arg.constructor === Array) {
-                    if (arg.length < 3) {
-                        result[i] = '[' + this.stringifyArguments(arg) + ']';
-                    } else {
-                        result[i] = '[' + this.stringifyArguments(slice.call(arg, 0, 1)) + '...' + this.stringifyArguments(slice.call(arg, -1)) + ']';
-                    }
-                } else if (arg.constructor === Object) {
-                    result[i] = '#object';
-                } else if (arg.constructor === Function) {
-                    result[i] = '#function';
-                } else if (arg.constructor === String) {
-                    result[i] = '"' + arg + '"';
-                } else if (arg.constructor === Number) {
-                    result[i] = arg;
-                }
-            }
-        }
-        return result.join(',');
-    },
-
-    sourceCache: {},
-
-    /**
-     * @return the text from a given URL
-     */
-    ajax: function(url) {
-        var req = this.createXMLHTTPObject();
-        if (req) {
-            try {
-                req.open('GET', url, false);
-                req.send(null);
-                return req.responseText;
-            } catch (e) {
-            }
-        }
-        return '';
-    },
-
-    /**
-     * Try XHR methods in order and store XHR factory.
-     *
-     * @return <Function> XHR function or equivalent
-     */
-    createXMLHTTPObject: function() {
-        var xmlhttp, XMLHttpFactories = [
-            function() {
-                return new XMLHttpRequest();
-            }, function() {
-                return new ActiveXObject('Msxml2.XMLHTTP');
-            }, function() {
-                return new ActiveXObject('Msxml3.XMLHTTP');
-            }, function() {
-                return new ActiveXObject('Microsoft.XMLHTTP');
-            }
-        ];
-        for (var i = 0; i < XMLHttpFactories.length; i++) {
-            try {
-                xmlhttp = XMLHttpFactories[i]();
-                // Use memoization to cache the factory
-                this.createXMLHTTPObject = XMLHttpFactories[i];
-                return xmlhttp;
-            } catch (e) {
-            }
-        }
-    },
-
-    /**
-     * Given a URL, check if it is in the same domain (so we can get the source
-     * via Ajax).
-     *
-     * @param url <String> source url
-     * @return False if we need a cross-domain request
-     */
-    isSameDomain: function(url) {
-        return url.indexOf(location.hostname) !== -1;
-    },
-
-    /**
-     * Get source code from given URL if in the same domain.
-     *
-     * @param url <String> JS source URL
-     * @return <Array> Array of source code lines
-     */
-    getSource: function(url) {
-        // TODO reuse source from script tags?
-        if (!(url in this.sourceCache)) {
-            this.sourceCache[url] = this.ajax(url).split('\n');
-        }
-        return this.sourceCache[url];
-    },
-
-    guessAnonymousFunctions: function(stack) {
-        for (var i = 0; i < stack.length; ++i) {
-            var reStack = /\{anonymous\}\(.*\)@(.*)/,
-                reRef = /^(.*?)(?::(\d+))(?::(\d+))?(?: -- .+)?$/,
-                frame = stack[i], ref = reStack.exec(frame);
-
-            if (ref) {
-                var m = reRef.exec(ref[1]), file = m[1],
-                    lineno = m[2], charno = m[3] || 0;
-                if (file && this.isSameDomain(file) && lineno) {
-                    var functionName = this.guessAnonymousFunction(file, lineno, charno);
-                    stack[i] = frame.replace('{anonymous}', functionName);
-                }
-            }
-        }
-        return stack;
-    },
-
-    guessAnonymousFunction: function(url, lineNo, charNo) {
-        var ret;
-        try {
-            ret = this.findFunctionName(this.getSource(url), lineNo);
-        } catch (e) {
-            ret = 'getSource failed with url: ' + url + ', exception: ' + e.toString();
-        }
-        return ret;
-    },
-
-    findFunctionName: function(source, lineNo) {
-        // FIXME findFunctionName fails for compressed source
-        // (more than one function on the same line)
-        // TODO use captured args
-        // function {name}({args}) m[1]=name m[2]=args
-        var reFunctionDeclaration = /function\s+([^(]*?)\s*\(([^)]*)\)/;
-        // {name} = function ({args}) TODO args capture
-        // /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function(?:[^(]*)/
-        var reFunctionExpression = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*function\b/;
-        // {name} = eval()
-        var reFunctionEvaluation = /['"]?([0-9A-Za-z_]+)['"]?\s*[:=]\s*(?:eval|new Function)\b/;
-        // Walk backwards in the source lines until we find
-        // the line which matches one of the patterns above
-        var code = "", line, maxLines = Math.min(lineNo, 20), m, commentPos;
-        for (var i = 0; i < maxLines; ++i) {
-            // lineNo is 1-based, source[] is 0-based
-            line = source[lineNo - i - 1];
-            commentPos = line.indexOf('//');
-            if (commentPos >= 0) {
-                line = line.substr(0, commentPos);
-            }
-            // TODO check other types of comments? Commented code may lead to false positive
-            if (line) {
-                code = line + code;
-                m = reFunctionExpression.exec(code);
-                if (m && m[1]) {
-                    return m[1];
-                }
-                m = reFunctionDeclaration.exec(code);
-                if (m && m[1]) {
-                    //return m[1] + "(" + (m[2] || "") + ")";
-                    return m[1];
-                }
-                m = reFunctionEvaluation.exec(code);
-                if (m && m[1]) {
-                    return m[1];
-                }
-            }
-        }
-        return '(?)';
-    }
-};
diff --git a/test.sh b/test.sh
index 9ca252e..3194c72 100755 (executable)
--- a/test.sh
+++ b/test.sh
@@ -16,4 +16,9 @@ if [ $? != 0 ]; then
   exit 1
 fi
 
-phantomjs phantom-driver.js $*
+phantomjs phantom-driver.js $* | tee /tmp/test-results.txt
+trap "rm -f /tmp/test-results.txt" EXIT
+if grep -q 'FAIL' /tmp/test-results.txt; then
+  echo One or more tests failed.
+  exit 1
+fi
index 0f0ec3c..127ec04 100644 (file)
@@ -98,7 +98,8 @@
         var shapes = [];
         var addShape = function(name, pointFn, highlightPointFn) {
           shapes.push(name);
-          opts[name] = {
+          if (!opts['series']) opts['series'] = {};
+          opts.series[name] = {
             drawPointCallback: pointFn,
             drawHighlightPointCallback: highlightPointFn
           };
diff --git a/tests/dense-fill.html b/tests/dense-fill.html
new file mode 100644 (file)
index 0000000..5d64998
--- /dev/null
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>dense, filled plots</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+    <style>
+    .chart {
+      width: 800px;
+      height: 500px;
+    }
+    </style>
+  </head>
+  <body>
+  <p>These charts are substantially sped up by <a href="https://github.com/danvk/dygraphs/pull/462/">down-sampling.</a></p>
+    <div class="chart" data-opts='{"fillGraph":true}'></div>
+
+    <p>step plot, filled</p>
+    <div class="chart" data-opts='{"fillGraph":true,"stepPlot":true}'></div>
+
+    <script>
+      var data = [];
+      for (var i = 0; i < 10000; i++) {
+        data.push([i, Math.sin(i/1000), Math.cos(i/1000)]);
+      }
+
+      var chartDivs = document.querySelectorAll('.chart');
+      for (var i = 0; i < chartDivs.length; i++) {
+        var chartDiv = chartDivs[i];
+        var opts = {labels: ['X', 'sin', 'cos'], animatedZooms: true};
+        var thisOpts = JSON.parse(chartDiv.getAttribute('data-opts'));
+        for (var k in thisOpts) {
+          opts[k] = thisOpts[k];
+        }
+
+        new Dygraph(chartDivs[i], data, opts);
+      }
+    </script>
+  </body>
+</html>
index d27dd85..fceab6a 100644 (file)
     -->
     <script type="text/javascript" src="../dygraph-dev.js"></script>
 
-
-    <script type="text/javascript">
+    <script>
     var start_date = new Date("2002/12/29").getTime();
     var end_date = new Date().getTime();
     data = [];
     for (var d = start_date; d < end_date; d += 604800 * 1000) {
       var millis = d + 2 * 3600 * 1000;
-      data.push( [ new Date(new Date(millis).strftime("%Y/%m/%d")), 50 ]);
+      var date = new Date(millis);
+      var yyyy = date.getFullYear(),
+          mm = date.getMonth(),
+          dd = date.getDate();
+      data.push( [ new Date(Date.UTC(yyyy, mm, dd)), 50 ]);
     }
     </script>
-    <style type="text/css">
+
+    <style>
     #tool_zoom {
       background: url('drawing/tool-palette.png');
       background-position: 0px 0px;
@@ -64,8 +68,9 @@
     var valueRange = [0, 100];
 
     function setPoint(event, g, context) {
-      var canvasx = Dygraph.pageX(event) - Dygraph.findPosX(g.graphDiv);
-      var canvasy = Dygraph.pageY(event) - Dygraph.findPosY(g.graphDiv);
+      var pos = Dygraph.findPos(g.graphDiv);
+      var canvasx = Dygraph.pageX(event) - pos.x;
+      var canvasy = Dygraph.pageY(event) - pos.y;
       var xy = g.toDataCoords(canvasx, canvasy);
       var x = xy[0], value = xy[1];
       var rows = g.numRows();
         {
           valueRange: valueRange,
           labels: [ 'Date', 'Value' ],
+          labelsUTC: true,
           interactionModel: {
             mousedown: function (event, g, context) {
               if (tool == 'zoom') {
             },
             mousemove: function (event, g, context) {
               if (tool == 'zoom') {
-                Dygraph.defaultInteractionModel.mousemove(event, g, context);
               } else {
                 if (!isDrawing) return;
                 setPoint(event, g, context);
             },
             mouseup: function(event, g, context) {
               if (tool == 'zoom') {
-                Dygraph.defaultInteractionModel.mouseup(event, g, context);
               } else {
                 finishDraw();
               }
             },
             mouseout: function(event, g, context) {
               if (tool == 'zoom') {
-                Dygraph.defaultInteractionModel.mouseout(event, g, context);
               }
             },
             dblclick: function(event, g, context) {
           },
           strokeWidth: 1.5,
           gridLineColor: 'rgb(196, 196, 196)',
-          drawYGrid: false,
-          drawYAxis: false
+          axes: {
+            y: {
+              drawAxis: false,
+              drawGrid: false
+            }
+          }
         });
         window.onmouseup = finishDraw;
   </script>
index 17dadcd..3dfe4bb 100644 (file)
         <input type="radio" id="rand" name="group1" value="rand"
           onclick="setDataType(this);"> random points <br></p>
       <p>Timestamps:
-        <input type="radio" id="aligned" name="timestamps" value="aligned"
-          onclick="setTimestampType(this);" checked> aligned
-        <input type="radio" id="unaligned" name="timestamps" value="unaligned"
-          onclick="setTimestampType(this);"> unaligned</p>
+        <input type="radio" id="aligned" name="timestamps" value="aligned" checked> aligned
+        <input type="radio" id="unaligned" name="timestamps" value="unaligned"> unaligned
+      </p>
+      <p>x-axis type:
+        <input type="radio" id="numeric" name="x-axis-type" value="numeric" onclick="setXAxisType(this)" checked> numeric
+        <input type="radio" id="dates" name="x-axis-type" value="date" onclick="setXAxisType(this)"> date/time
+      <p><input type="checkbox" id="fill"><label for="fill"> Fill?</label></p>
       <p>Number of points per series (points):
          <input type="text" id="points" size="20"></p>
       <p>Number of series (series):
       var metrics = null;
       var dataType = "sine";
       var timestamps = "aligned";
+      var numRuns = 0;
 
       var durations = [];
       updatePlot = function() {
         document.getElementById('message').innerHTML = "";
         var plotDiv = document.getElementById('plot');
         plotDiv.innerHTML = 'Redrawing...';
+        var numeric = document.getElementById('numeric').checked;
         var numPoints =
             parseInt(document.getElementById('points').value);
         var numSeries =
@@ -63,8 +68,8 @@
             parseInt(document.getElementById('repetitions').value);
 
         var data = [];
-        var xmin = 0.0;
-        var xmax = 2.0 * Math.PI;
+        var xmin = numeric ? 0.0 : Date.parse("2014/01/01");
+        var xmax = numeric ? 2.0 * Math.PI : Date.parse("2014/12/31");
         var adj = .5;
         var delta = (xmax - xmin) / (numPoints - 1);
         var unalignmentDelta = delta / numSeries;
@@ -90,6 +95,7 @@
               data[i*numSeries + j] = elemCopy;
             }
           }
+          if (!numeric) data[i][0] = new Date(data[i][0]);
         }
         var labels = [ "x" ];
         for (var j = 0; j < numSeries; j++) {
         var rollPeriod = parseInt(
             document.getElementById('rollPeriod').value);
         var opts = {labels: labels, rollPeriod: rollPeriod, timingName: "x"};
+        opts['fillGraph'] = document.getElementById('fill').checked;
         var millisecondss = [];
         for (var i = 0; i < repetitions; i++) {
           if (graph != null) {
           var start = new Date();
           graph = new Dygraph(plotDiv, data, opts);
           var end = new Date();
-          durations.push([start, end - start]);
+          durations.push([numRuns++, end - start]);
           millisecondss.push(end - start);
         }
         if (repetitions == 1) {
           }
           avg/=millisecondss.length; 
           document.getElementById('message').innerHTML =
-              "Durations: " + millisecondss + " Average: " + avg;
+              "Durations: " + millisecondss + "<br>Average: " + avg;
         }
 
         if (durations.length > 0) {
index 0188dc4..2b048df 100644 (file)
@@ -59,7 +59,9 @@
           return ret;
         },
         {
-          Y1: { fillGraph: true }
+          series: {
+            Y1: { fillGraph: true }
+          }
         }
       );
     </script>
diff --git a/tests/labelsDateUTC.html b/tests/labelsDateUTC.html
new file mode 100644 (file)
index 0000000..3835f81
--- /dev/null
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>UTC date labels</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <!--
+    For production (minified) code, use:
+    <script type="text/javascript" src="dygraph-combined.js"></script>
+    -->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+  </head>
+  <body>
+
+    <h2>UTC date and time labels</h2>
+
+    <p>This shows how date ticks and labels may be generated according to local 
+    time (default) or UTC with the option <code>labelsUTC</code>.</p>
+    
+    <p>72 hours of random hourly data since 2009-Jul-23 18:00 UTC 
+    according to local time (top) and UTC (bottom):</p>
+    
+    <div id="div_loc" style="width:600px; height:200px;"></div>
+    <div id="div_utc" style="width:600px; height:200px;"></div>
+    
+    <p>Please note the offset between the respective ticks in both plots.
+    It should match your local time zone offset.</p>
+    
+    <p>You can also check it by hovering over corresponding points and comparing
+    the value labels.</p>
+    
+    <p>Try different zoom levels to show that ticks are always placed at nice
+    time boundaries.</p>
+
+    <script type="text/javascript">
+      var data = (function() {
+        var rand10 = function () { return Math.round(10 * Math.random()); };
+        var a = []
+        for (var y = 2009, m = 6, d = 23, hh = 18, n=0; n < 72; n++) {
+          a.push([new Date(Date.UTC(y, m, d, hh + n, 0, 0)), rand10()]);
+        }
+        return a;
+      })();
+      gloc = new Dygraph(
+                   document.getElementById("div_loc"),
+                   data,
+                   {
+                     labels: ['local time', 'random']
+                   }
+                 );
+      gutc = new Dygraph(
+                   document.getElementById("div_utc"),
+                   data,
+                   {
+                     labelsUTC: true,
+                     labels: ['UTC', 'random']
+                   }
+                 );
+    </script>
+
+  </body>
+</html>
index 5e79860..f44df53 100644 (file)
         updateChart();
       }
 
+      function toHex(rgb) {
+        return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
+      }
+
       function updateChart() {
         // Generate a new data set with the regression lines.
         var new_labels = [];
             // Darken the series by 50% to generate its regression.
             var label = labels[i] + " Regression";
             new_labels.push(label);
-            var c = new RGBColorParser(orig_colors[i - 1]);
+            var c = Dygraph.toRGB_(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_colors.push(toHex(c));
             new_opts[label] = {
               drawPoints: false,
               strokeWidth: 1.0
index 253fa60..8b3a6d8 100644 (file)
@@ -42,7 +42,8 @@
                 drawPoints: true,
                 strokeWidth: 0.0,
                 fractions: true,
-                errorBars: true
+                errorBars: true,
+                pointSize: 1
               }
           );
 
         g.updateOptions({});
       }
 
+      function toHex(rgb) {
+        return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
+      }
+
       function drawLines(ctx, area, layout) {
         if (typeof(g) == 'undefined') return;  // won't be set on the initial draw.
 
           var p1 = g.toDomCoords(x1, y1);
           var p2 = g.toDomCoords(x2, y2);
 
-          var c = new RGBColorParser(g.getColors()[i - 1]);
+          var c = Dygraph.toRGB_(g.getColors()[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));
-          var color = c.toHex();
+          var color = toHex(c);
           ctx.save();
           ctx.strokeStyle = color;
-          ctx.lineWidth = 1.0;
+          ctx.lineWidth = 2.0;
           ctx.beginPath();
           ctx.moveTo(p1[0], p1[1]);
           ctx.lineTo(p2[0], p2[1]);
index d7c3700..41cfbf3 100644 (file)
         g.updateOptions({});
       }
 
+      function toHex(rgb) {
+        return 'rgb(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ')';
+      }
+
       function drawLines(ctx, area, layout) {
         if (typeof(g) == 'undefined') return;  // won't be set on the initial draw.
 
           var p1 = g.toDomCoords(x1, y1);
           var p2 = g.toDomCoords(x2, y2);
 
-          var c = new RGBColorParser(g.getColors()[i - 1]);
+          var c = Dygraph.toRGB_(g.getColors()[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));
-          var color = c.toHex();
+          var color = toHex(c);
           ctx.save();
           ctx.strokeStyle = color;
           ctx.lineWidth = 1.0;
index 98193ba..85818fd 100644 (file)
 
   <body>
     <center>
-           <input id='log' type="button" value="log scale" onclick="setLogScale(true)">
-           <input id='linear' type="button" value="linear scale" onclick="setLogScale(false)">
+      <input id='ylog' type="button" value="y log scale" onclick="setLogScale('y', true)">
+      <input id='ylinear' type="button" value="y linear scale" onclick="setLogScale('y', false)">
+      <input id='xlog' type="button" value="x log scale" onclick="setLogScale('x', true)">
+      <input id='xlinear' type="button" value="x linear scale" onclick="setLogScale('x', false)">
+      <div>Current scales: <span id="description"></span></div>
     </center>
 
     <h2>X axis of dates</h2>
     <div id="div_g0" style="width:600px; height:300px;"></div>
+    <div style="font-style: italic; margin-left: 40px;">(Note: when the x-axis is dates, logscale is ignored on that axis.)</div>
 
     <h2>X axis of numbers</h2>
     <div id="div_g1" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
+      Dygraph.Interaction.DEBUG = true;
+
       function data0() {
         return "Date,A\n" +
         "20101201,5\n"+
         "8,100\n"+
         "9,500\n"+
         "101,500\n"+
+        "30,500\n"+
+        "50,400\n"+
+        "100,300\n"+
+        "300,200\n"+
+        "1000,100\n"+
         "";
       };
 
-      var g0 = new Dygraph(document.getElementById("div_g0"),
-                      data0, { logscale : true });
-      var g1 = new Dygraph(document.getElementById("div_g1"),
-                      data1, { logscale : true });
-      function setLogScale(val) {
-        g0.updateOptions({ logscale: val });
-        g1.updateOptions({ logscale: val });
-        document.getElementById("linear").disabled = !val;
-        document.getElementById("log").disabled = val;
+      var g0 = new Dygraph(document.getElementById("div_g0"), data0, {});
+      var g1 = new Dygraph(document.getElementById("div_g1"), data1, {});
+      var graphs = [ g0, g1 ];
+      var scales = { x : false, y : false };
+      function setLogScale(axis, val) {
+        if (axis === 'y') {
+          for (var idx = 0; idx < graphs.length; idx++) {
+            graphs[idx].updateOptions({ logscale: val });
+          }
+        } else {
+          for (var idx = 0; idx < graphs.length; idx++) {
+            graphs[idx].updateOptions({ axes : { x : {  logscale : val } } });
+          }
+        }
+        scales[axis] = val;
+        var text = "y: " + (scales.y ? "log" : "linear") + ", x: " + (scales.x ? "log" : "linear");
+        document.getElementById("description").innerText = text;
       }
+
+      setLogScale('y', true);
     </script>
 
   </body>
index 84730b1..4f436d6 100644 (file)
               {
                 legend: 'always',
                 strokeWidth: 2,
-                'parabola': {
-                  strokePattern: null,
-                  drawPoints: true,
-                  pointSize: 4,
-                  highlightCircleSize: 6
-                },
-                'line': {
-                  strokePattern: Dygraph.DASHED_LINE,
-                  strokeWidth: 1.0,
-                  drawPoints: true,
-                  pointSize: 1.5
-                },
-                'another line': {
-                  strokePattern: [25, 5]
-                },
-                'sine wave': {
-                  strokePattern: Dygraph.DOTTED_LINE,
-                  strokeWidth: 3,
-                  highlightCircleSize: 10
-                },
-                'sine wave2': {
-                  strokePattern: Dygraph.DOT_DASH_LINE,
-                  strokeWidth: 2,
-                  highlightCircleSize: 3
+                series: {
+                  'parabola': {
+                    strokePattern: null,
+                    drawPoints: true,
+                    pointSize: 4,
+                    highlightCircleSize: 6
+                  },
+                  'line': {
+                    strokePattern: Dygraph.DASHED_LINE,
+                    strokeWidth: 1.0,
+                    drawPoints: true,
+                    pointSize: 1.5
+                  },
+                  'another line': {
+                    strokePattern: [25, 5]
+                  },
+                  'sine wave': {
+                    strokePattern: Dygraph.DOTTED_LINE,
+                    strokeWidth: 3,
+                    highlightCircleSize: 10
+                  },
+                  'sine wave2': {
+                    strokePattern: Dygraph.DOT_DASH_LINE,
+                    strokeWidth: 2,
+                    highlightCircleSize: 3
+                  }
                 }
               }
           );
               data,
               {
                 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
-                },
-                'sine wave2': {
-                  strokePattern: [10, 2, 5, 2],
-                  strokeWidth: 2,
-                  highlightCircleSize: 3
+                series: {
+                  '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
+                  },
+                  'sine wave2': {
+                    strokePattern: [10, 2, 5, 2],
+                    strokeWidth: 2,
+                    highlightCircleSize: 3
+                  }
                 }
               }
           );
index 30a4fd9..5bbbf15 100644 (file)
@@ -51,6 +51,9 @@
     and showing error bars only for some series.</p>
     <div id="mixed-error" class="chart"></div>
 
+    <h2>Smooth Lines</h2>
+    <p>See the <a href="smooth-plots.html">smooth-plots demo</a> for an example of a custom plotter which connects points using bezier curves instead of straight lines.</p>
+
     <script type="text/javascript">
       // Darken a color
       function darkenColor(colorStr) {
@@ -243,11 +246,13 @@ var candleData = "Date,Open,Close,High,Low\n" +
             {
               labels: ['Date', 'A', 'B'],
               includeZero: true,
-              "A": {
-                strokeWidth: 2
-              },
-              "B": {
-                plotter: barChartPlotter
+              series: {
+                "A": {
+                  strokeWidth: 2
+                },
+                "B": {
+                  plotter: barChartPlotter
+                }
               }
             });
 
@@ -311,12 +316,14 @@ var candleData = "Date,Open,Close,High,Low\n" +
             NoisyData(),
             {
               errorBars: true,
-              'A': {
-                plotter: Dygraph.Plotters.errorPlotter
-              },
-              'B': {
-                plotter: Dygraph.Plotters.linePlotter,
-                strokePattern: Dygraph.DASHED_LINE
+              series: {
+                'A': {
+                  plotter: Dygraph.Plotters.errorPlotter
+                },
+                'B': {
+                  plotter: Dygraph.Plotters.linePlotter,
+                  strokePattern: Dygraph.DASHED_LINE
+                }
               }
             });
 
index 2d084d0..49a11cd 100644 (file)
@@ -9,7 +9,7 @@
     <script type="text/javascript" src="../dygraph-dev.js"></script>
 
     <!-- Include the Javascript for the plug-in -->
-    <script type="text/javascript" src="../plugins/unzoom.js"></script>
+    <script type="text/javascript" src="../extras/unzoom.js"></script>
   </head>
   <body>
     <h2>Plugins Demo</h2>
index 2cb9615..8527b5c 100644 (file)
     </p>
     <div id="roll14" style="width:800px; height:320px;"></div>
     <p>
+      Use the average of a specific subset of series to draw the mini plot (only the first series is used in this test).
+      The default behaviour is to compute the average of <em>all</em> series.
+    </p>
+    <div id="selectcombined" style="width:800px; height:320px;"></div>
+    <p>
       Demo of range selecor without the chart. (interesting if multiple charts should be synced with one range selector).
     </p>
     <div id="nochart" style="width:800px; height:30px;"></div>
+    <p>Demo of range selector with stepPlot</p>
+    <div id="stepplot" style="width:800px; height:320px;"></div>
+
     <script type="text/javascript">
       g1 = new Dygraph(
           document.getElementById("noroll"),
           }
       );
       g3 = new Dygraph(
+          document.getElementById("selectcombined"),
+          [
+            [0, 1, 4, 10],
+            [10, 2, 8, 19],
+            [25, 15, 4, 2],
+            [35, 0, 3, 2]
+          ],
+          {
+            title: 'Daily Temperatures in New York vs. San Francisco',
+            ylabel: 'Temperature (F)',
+            showRangeSelector: true,
+            labels: ['X', 'Y1', 'Y2', 'Y3'],
+            series: {
+              'Y1': { showInRangeSelector: true }
+            }
+          }
+      );
+      g4 = new Dygraph(
           document.getElementById("nochart"),
           [[0,1],[10,1]],
           {
             rangeSelectorHeight: 30
           }
       );
+      g5 = new Dygraph(document.getElementById("stepplot"),
+                      "Date,Idle,Used\n" +
+                      "2008-05-07,70,30\n" +
+                      "2008-05-08,42,88\n" +
+                      "2008-05-09,88,42\n" +
+                      "2008-05-10,33,37\n" +
+                      "2008-05-11,30,35\n",
+                       {
+                          stepPlot: true,
+                          fillGraph: true,
+                          stackedGraph: true,
+                          includeZero: true,
+                          showRangeSelector: true
+                       });
     </script>
   </body>
 </html>
diff --git a/tests/smooth-plots.html b/tests/smooth-plots.html
new file mode 100644 (file)
index 0000000..175da2d
--- /dev/null
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7; IE=EmulateIE9">
+    <title>Plotters demo</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="../excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-dev.js"></script>
+    <script type="text/javascript" src="../extras/smooth-plotter.js"></script>
+
+    <style type="text/css">
+      body {
+        max-width: 700px;
+      }
+      div.chart {
+        width: 640px;
+        height: 320px;
+      }
+      input[type="range"] {
+        width: 400px;
+      }
+      .smoother {
+        margin-left: 50px;
+      }
+    </style>
+  </head>
+  <body>
+    <h2>Smooth Lines</h2>
+    <p>This plotter draws smooth lines between points using bezier curves.</p>
+    <p class="smoother">Smoothing:&nbsp;<input type="range" id="smoothing-amount" min=0 max=0.7 step=0.01 value=0.33> <span id="smoothing-param">0.33</span></p>
+    <div id="smooth-line" class="chart"></div>
+
+    <p>View source to see how this works. You'll have to source <code>extras/smooth-plotter.js</code> in addition to dygraphs to get this feature. See the <a href="https://github.com/danvk/dygraphs/pull/469">pull request</a> that introduced this plotter to learn more about how it smooths your curves.</p>
+
+<script type="text/javascript">
+// Smooth line plotter
+var functionData = [];
+var vs = [10, 20, 40, 0, 30, 15, 25, 60, 35, 45];
+for (var i = 0; i < 10; i++) {
+  var v = vs[i];
+  functionData.push([i, v, v]);
+}
+
+var g6;
+function drawSmoothPlot() {
+  g6 = new Dygraph(document.getElementById('smooth-line'),
+                   functionData,
+                   {
+                     labels: ['Year', 'Straight', 'Smoothed'],
+                     series: {
+                       Straight: {
+                         color: 'rgba(0,0,0,0.33)',
+                         strokeWidth: 2,
+                         drawPoints: true,
+                         pointSize: 3
+                       },
+                       Smoothed: {
+                         plotter: smoothPlotter,
+                         color: 'red',
+                         strokeWidth: 2
+                       }
+                     },
+                     legend: 'always',
+                     gridLineColor: '#ddd'
+                   });
+}
+drawSmoothPlot();
+
+var smoothRangeEl = document.getElementById('smoothing-amount');
+smoothRangeEl.addEventListener('input', function() {
+  var param = parseFloat(smoothRangeEl.value);
+  smoothPlotter.smoothing = param;
+  document.getElementById('smoothing-param').innerHTML = param;
+  drawSmoothPlot();
+});
+</script>
+</body>
+</html>
index 8c58811..dd9ff5c 100644 (file)
         labels: ["Date","GapSeries1","GapSeries2"],
         showRoller: true,
         stepPlot: true,
-        GapSeries2: { axis: {} }
+        series: {
+          GapSeries2: {
+            axis: 'y2'
+          }
+        }
       }
     );
     </script>
index 0646b9a..12e8025 100644 (file)
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
             series : {
-              'Y3': {
-                axis: {
-                }
-              },
-              'Y4': {
-                axis: 'Y3'  // use the same y-axis as series Y3
-              }
+              'Y3': { axis: 'y2' },
+              'Y4': { axis: 'y2' }
             },
             valueRange: [40, 70],
             axes: {
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
             series : {
-              'Y3': {
-                axis: {
-                }
-              },
-              'Y4': {
-                axis: 'Y3'  // use the same y-axis as series Y3
-              }
+              'Y3': { axis: 'y2' },
+              'Y4': { axis: 'y2' }
             },
             axes: {
               y: {
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
             series : {
-              'Y3': {
-                axis: {
-                }
-              },
-              'Y4': {
-                axis: 'Y3'  // use the same y-axis as series Y3
-              }
+              'Y3': { axis: 'y2' },
+              'Y4': { axis: 'y2' }
             },
             axes: {
               y: {
index e3e29ff..24f717a 100644 (file)
           data,
           {
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
-            'Y3': {
-              axis: {
-              }
-            },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
+            series: {
+              'Y3': {
+                axis: 'y2'
+              },
+              'Y4': {
+                axis: 'y2'
+              },
             },
             axes: {
               y2: {
index 193c48e..48d0f62 100644 (file)
                    1e6 * (2 - i * (100 - i) / (50 * 50))]);
       }
 
+      function formatDate(d) {
+        var yyyy = d.getFullYear(),
+            mm = d.getMonth() + 1,
+            dd = d.getDate();
+        return yyyy + '-' + (mm < 10 ? '0' : '') + mm + (dd < 10 ? '0' : '') + dd;
+      }
+
       g = new Dygraph(
           document.getElementById("demodiv"),
           data,
             labels: [ 'Date', 'Y1', 'Y2', 'Y3', 'Y4' ],
             width: 640,
             height: 350,
-            'Y3': {
-              axis: {
-              }
-            },
-            'Y4': {
-              axis: 'Y3'  // use the same y-axis as series Y3
+            series: {
+              'Y3': { axis: 'y2' },
+              'Y4': { axis: 'y2' }
             },
             xAxisLabelWidth: 100,
             yAxisLabelWidth: 100,
             axes: {
               x: {
                 valueFormatter: function(ms) {
-                  return 'xvf(' + new Date(ms).strftime('%Y-%m-%d') + ')';
+                  return 'xvf(' + formatDate(new Date(ms)) + ')';
                 },
                 axisLabelFormatter: function(d) {
-                  return 'xalf(' + d.strftime('%Y-%m-%d') + ')';
+                  return 'xalf(' + formatDate(d) + ')';
                 },
                 pixelsPerLabel: 100,
               },
index d216c97..9983391 100644 (file)
@@ -47,8 +47,8 @@
             { 
               axes: {
                 x: {
-                  axisLabelFormatter: function(d, gran) {
-                      return Dygraph.dateAxisFormatter(new Date(d.getTime() + 7200*1000), gran);
+                  axisLabelFormatter: function(d, gran, opts) {
+                      return Dygraph.dateAxisLabelFormatter(new Date(d.getTime() + 7200*1000), gran, opts);
                   }
                 }
               }