Datahandler and Unified Data Format
authorDavid Eberlein <david.eberlein@ch.sauter-bc.com>
Wed, 28 Aug 2013 02:55:47 +0000 (22:55 -0400)
committerDan Vanderkam <danvdk@gmail.com>
Wed, 28 Aug 2013 02:55:47 +0000 (22:55 -0400)
This commit unifies the various data formats used by dygraphs (error
bars, custom bars, fractions, simple numbers) into a single, canonical
format. It also allows users to create their own data formats.

Original proposal:
https://docs.google.com/document/d/1IWmX4oDbQbVtUoRNzSRG3yMpoBQ7LseVCQhGnuOZz_A/edit?pli=1#

This commit corresponds to pull request 249.
(https://github.com/danvk/dygraphs/pull/249)

Squashed commit of the following:

commit acf2bc1dcdede4a52a5a9530476fbaf031009244
Author: Dan Vanderkam <danvdk@gmail.com>
Date:   Tue Aug 27 22:50:46 2013 -0400

    update closure TODO with new files

commit 13116fcf57a47b878f5cae2348718d61eebdc2e4
Merge: 0668281 625324f
Author: Dan Vanderkam <danvdk@gmail.com>
Date:   Tue Aug 27 22:43:50 2013 -0400

    Merge branch 'sauter-custom-datahandler' of https://github.com/sauter-hq/dygraphs into 249-datahandler

commit 625324fb1ce5d172537e484408497aab8bb1fcf0
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Mon Aug 5 14:44:06 2013 +0200

    BUGFIX: Readded callback.js to the tests which was mistakenly removed.

commit d18669c853b3e906a614fd3e251b06ab60be5ae7
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Mon Aug 5 14:28:41 2013 +0200

    DOC: Refactored JSDoc to fit the closure complier standard.

commit a5bb18294faca0c411507b74bd9ee25ac364ccda
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Mon Jul 22 18:04:37 2013 +0200

    BUGFIX: Fixed lint errors for static usage of Dygraph and DygraphLayout

commit a7b2e9493011710b2cb602200be6c2e839ffd6c0
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon Jul 22 16:53:36 2013 +0200

    REFACTORING: Changed some method parameters based on suggestions of Dan and Robert and added some documentation.

commit 1d65e4effe51a00166a346cc06117d4613476231
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon Jul 22 15:16:07 2013 +0200

    REFACTORING: Renamed datahandler files

commit edb9bdfac79a810f5e0ae1758062e63bb21e8b5d
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon Jul 22 15:12:55 2013 +0200

    DOC: Added documentation for data handlers and fixed wrong method visibilities.

commit 9ed5d88e2bfe2518293565cc97ee06bf56040c72
Merge: af5decb 22bc1f1
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Jul 19 10:09:28 2013 +0200

    Merge branch 'official-master' into sauter-custom-datahandler

    Conflicts:
     dygraph.js

commit af5decb0e678e57eb9a50bfc7b141b5d647be1a1
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Wed Jul 10 10:21:25 2013 +0200

    REFACTORING: Extracted getHandlerId_ method and refactored some variable names for better readability

commit 84be5b402420a10013ebf2e49943e8faabf54b01
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Tue Jul 9 18:02:45 2013 +0200

    Fixed formatting issues.

commit b2085b50a3f16d4067b02a1a2069f44835476d3d
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Tue Jun 25 08:49:54 2013 +0200

    REFACTORING: Romoved old extract series method which is now moved into the data handlers.

commit 7d851638386c9e94bbd593a2bee7bfdec8486753
Merge: c8f5edb 464538e
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Jun 20 15:13:22 2013 +0200

    Merge branch 'official-master' into sauter-custom-datahandler

    Conflicts:
     dygraph.js

commit c8f5edbb690ffdc3bc88393f26f8d3fd8f717533
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Jun 14 12:20:41 2013 +0200

    FEATURE: Integreated the new creation of points into the data handlers. This proposal is now fully functional again.

commit 5c1ce9c76243c430f9f5d50864b9cb909f611f6f
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Jun 14 12:19:33 2013 +0200

    Reverted file EOL back to unix

commit 2684ae56b8e2881680771f783449b3fd77903626
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Jun 14 12:18:02 2013 +0200

    BUGFIX: Fixed incorrect handling of logscale case in the datahandlers extractSeries methods.

commit bcb28c102b91e902a881abdec6cf29b327610235
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Jun 14 11:06:34 2013 +0200

    CLEAN: Removed old imports of sauter js files.

commit 565afa87132d521a18dc7286bd4f79c6a1b743f8
Merge: 40f9f50 95a9512
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Jun 13 18:07:16 2013 +0200

    Merge branch 'official-master' into sauter-custom-datahandler: This is not yet functional, Dygraphs.seriesToPoints, and DygraphsLayout.evaluteLineCharts must still be refactored.

    Conflicts:
     dygraph-layout.js
     dygraph.js

commit 40f9f50c2d2f1af65bb2ece54311d424d54a3afa
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon Jun 3 09:44:29 2013 +0200

    REFACTORING: Removed benchmark tests since they are now integrated into the dygraphs-perf project.

commit 12940792fa7999da6910947cb7d0f407dd8ad81f
Merge: c24f3f3 8cf57f1
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon Jun 3 09:41:16 2013 +0200

    Merge remote-tracking branch 'official/master' into sauter-custom-datahandler

commit c24f3f3832a906d9c0c5a61173a94e26150b188b
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Thu May 30 17:10:57 2013 +0200

    REFACTORING: Fixed lint issues and broken test as well as some code style for the pull.

commit ea03a641f42d42d54842111e5f337c39ea3156ef
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu May 30 16:51:42 2013 +0200

    CLEANUP: Removed sauter specific data handlers and the not 100% finished Bugfix for the y extremes date window issue so that this can be pulled.

commit 3782b55d0f66a600fe863ba3cb2a6c1588839525
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Thu May 16 13:51:09 2013 +0200

    BUGFIX: Deleted falsely added comma

commit 803715042a4ba225097411be78f89f0c46df9236
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Thu May 16 13:47:58 2013 +0200

    OPTIMIZATION: Replaced DataHandler onPointCreated callback with
    onLineEvaluted passing all created points for one series instead of
    calling the other method for each created point. Also added some bug
    fixes to the yextemes method.

commit 97efdf6a4d01c8e888c614025a55252c0a55fa8d
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Wed May 15 11:26:17 2013 +0200

    FEATURE: Made the onPointCreated callback optional for better
    performance

commit 0b7892c335ab4309325efecaff95f7d6d8368848
Merge: c15adff 1bffeb9
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Wed May 15 11:02:46 2013 +0200

    Merge branch 'sauter-custom-datahandler' of https://github.com/sauter-hq/dygraphs into sauter-custom-datahandler

commit 1bffeb9e60da0d1123cca874ca00cd86d53dfb2d
Author: eberldav <eberldav@ch.sauter-bc.com>
Date:   Wed May 15 11:01:00 2013 +0200

    REFACTORING: Removed unneseccary fractions check in default data
    handler.

commit c15adff4d2f857b84dfc231e0c19c5616c48d95f
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Wed May 15 10:58:05 2013 +0200

    FEATURE: Removed copying the rolled series in the gather data method since the previous bug is fixed with the new data handler concept

commit 7c9d9f243a6b3149c495e1194baeaee1c6e1f479
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 14:56:22 2013 +0200

    BUGFIX: Fixed wrong call of DataHandlers

commit 88f7311197bc5896a693a37aa52997c4be700454
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 13:48:24 2013 +0200

    Added data handler to the generate combined and jsTestDriver imports.

commit 4eb44304546ae705e41cc90f7f6f027846d33968
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 11:18:36 2013 +0200

    BUGFIX: fixed wrong value comparison for data pruning.

commit c21c7f09bd545f6ad2a99e87d9f1584ceafd4c3e
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 11:06:04 2013 +0200

    BUGFIX: Fixed not complete adaption of pruning.

commit f5fcd68c8fad65ac7008972dfe8ed1b92868b5d2
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 10:51:32 2013 +0200

    REFACTORING: Adapted samples pruning to the unified data format.

commit 34bf8984e75437f4e0f59f5f1f18cd041308d4b4
Merge: 925e74a b839102
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Mon May 13 10:42:55 2013 +0200

    Merge branch 'official-master' into sauter-custom-datahandler

    Conflicts:
     auto_tests/misc/local.html

commit 925e74a6116c988060f83a78d34a4a60853a01e4
Merge: f9c4250 9f890c2
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri May 3 16:07:07 2013 +0200

    Merge commit '9f890c23ad80924d0a30f3a14f8680b7c2d6318e' into sauter-custom-datahandler

commit f9c4250752b8a1d71aa8a37b69ca8a2bef42cd98
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Apr 25 16:42:34 2013 +0200

    TEST: Added benchmark test allowing for different dygaphs benchmarks. They are currently deactivated because they take quite a while to run.

commit e6de1648346eaec0f253ef73836bf5c0e24a347f
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Apr 25 14:29:59 2013 +0200

    TEST: Adapted tests to conform to the new format and wrote new tests for the rolling options.

commit 8e8161efa9d79ef237a067f52f346dd1e36254ac
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Apr 25 14:29:03 2013 +0200

    FEATURE: Adapted DataHandler proposal to implement a unified data format.

commit 5667ec0d745ec446f29a444246a9c0c4555cbc53
Merge: 1049bc4 de25451
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Thu Apr 25 09:19:40 2013 +0200

    Merge commit 'de2545148870a1bdb0957c4c42e80bdb8ce1656d' into sauter-custom-datahandler

commit 1049bc479f0245f35453110f74eb03fd5919a642
Merge: 531fd88 d88dec8
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Tue Apr 23 18:17:37 2013 +0200

    Merge commit 'd88dec82afd6b902ffa56339d4afbf3277ad5ba3' into sauter-custom-datahandler

commit 531fd88e2fcae671f59e62b771df54310db1085d
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 16:15:59 2013 +0200

    FEATURE: removed dygraphs-layout.evaluteWithErrors and added its content to the datahandler-bars.

commit 5235abea1f1f77668c15e6f1c44be185f00fe5e2
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 16:06:36 2013 +0200

    REFACTORING: Reordered the datahandler methods to fit the calling order

commit e8c157be072cb3b1bc58e105311e3354555b5ea0
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 15:47:19 2013 +0200

    DOC: Added documentation and minor fixes to the datahandler

commit 58c69c1e5defe57d92b19cfbbf8ab49b8acba163
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 15:36:32 2013 +0200

    REFACTORING: Minor enhancments in extremes computation

commit 3ab152656a0f46239b13bac5aa854381965c907f
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 13:42:31 2013 +0200

    BUGFIX: Fixed bug still using the customData option in dygraph-layout.

commit 4bea6089553269046e80c18b6bd9a77beff3ed92
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 12:24:29 2013 +0200

    BUGFIX: Removed wrong code in bars datahandler callback

commit 442151876269da341a3932750a7e11a52e594d0f
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 11:43:57 2013 +0200

    TESTS: Adapted tests to fit the new dataHandler integration

commit ce7550aaf00a2cafbdc1be3abc4f12dc7ea3a687
Author: David Eberlein <david.eberlein@ch.sauter-bc.com>
Date:   Fri Apr 19 11:42:52 2013 +0200

    FEATURE: Integrated the dataHandler model into dygraphs.

commit e13eb4e4ea0514e62cba89975ed6b8aadebfad0e
Author: eichsjul <julian.eichstaedt@ch.sauter-bc.com>
Date:   Thu Apr 18 15:38:36 2013 +0200

    BUGFIX: Moved extremeValues method to the correct place and added the needed arguments to the call.

commit 05afa764addc217e87cf1a29ff350517207f7f65
Author: eichsjul <julian.eichstaedt@ch.sauter-bc.com>
Date:   Thu Apr 18 14:51:47 2013 +0200

    FEATURE: Added initial implementation of custom data support.

16 files changed:
auto_tests/tests/error_bars.js
auto_tests/tests/rolling_average.js
closure-todo.txt
datahandler/bars-custom.js [new file with mode: 0644]
datahandler/bars-error.js [new file with mode: 0644]
datahandler/bars-fractions.js [new file with mode: 0644]
datahandler/bars.js [new file with mode: 0644]
datahandler/datahandler.js [new file with mode: 0644]
datahandler/default-fractions.js [new file with mode: 0644]
datahandler/default.js [new file with mode: 0644]
dygraph-dev.js
dygraph-layout.js
dygraph.js
generate-combined.sh
jsTestDriver.conf
lint.sh

index 1e9d0f4..1c3894c 100644 (file)
@@ -135,8 +135,8 @@ errorBarsTestCase.prototype.testErrorBarsCorrectColors = function() {
 // Regression test for http://code.google.com/p/dygraphs/issues/detail?id=392
 errorBarsTestCase.prototype.testRollingAveragePreservesNaNs = function() {
   var graph = document.getElementById("graph");
-  var g = new Dygraph(graph,
-  [
+  var data = 
+    [
       [1, [null, null], [3,1]],
       [2, [2, 1], [null, null]],
       [3, [null, null], [5,1]],
@@ -145,8 +145,9 @@ errorBarsTestCase.prototype.testRollingAveragePreservesNaNs = function() {
       [6, [NaN, NaN], [null, null]],
       [8, [8, 1], [null, null]],
       [10, [10, 1], [null, null]]
-     ]
-        , {
+    ];
+  var g = new Dygraph(graph, data,
+        {
           labels: ['x', 'A', 'B' ],
           connectSeparatedPoints: true,
           drawPoints: true,
@@ -154,26 +155,20 @@ errorBarsTestCase.prototype.testRollingAveragePreservesNaNs = function() {
         }
       );
 
-  var in_series = [
-    [1, [null, null]],
-    [2, [2, 1]],
-    [3, [null, null]],
-    [4, [4, 0.5]],
-    [5, [null, null]],
-    [6, [NaN, NaN]],
-    [8, [8, 1]],
-    [10, [10, 1]]
-  ];
-  assertEquals(null, in_series[4][1][0]);
-  assertEquals(null, in_series[4][1][1]);
-  assertNaN(in_series[5][1][0]);
-  assertNaN(in_series[5][1][1]);
-
-  var out_series = g.rollingAverage(in_series, 1);
-  assertNaN(out_series[5][1][0]);
-  assertNaN(out_series[5][1][1]);
-  assertNaN(out_series[5][1][2]);
-  assertEquals(null, out_series[4][1][0]);
-  assertEquals(null, out_series[4][1][1]);
-  assertEquals(null, out_series[4][1][1]);
+  var in_series = g.dataHandler_.extractSeries(data, 1, g.attributes_);
+
+  assertEquals(null, in_series[4][1]);
+  assertEquals(null, in_series[4][2][0]);
+  assertEquals(null, in_series[4][2][1]);
+  assertNaN(in_series[5][1]);
+  assertNaN(in_series[5][2][0]);
+  assertNaN(in_series[5][2][1]);
+
+  var out_series = g.dataHandler_.rollingAverage(in_series, 1, g.attributes_);
+  assertNaN(out_series[5][1]);
+  assertNaN(out_series[5][2][0]);
+  assertNaN(out_series[5][2][1]);
+  assertEquals(null, out_series[4][1]);
+  assertEquals(null, out_series[4][2][0]);
+  assertEquals(null, out_series[4][2][1]);
 };
index c0af8f9..86b3e3c 100644 (file)
@@ -87,16 +87,150 @@ rollingAverageTestCase.prototype.testRollShortFractions = function() {
     customBars: true,
     labels: ['x', 'A']
   };
-  var data1 = [ [1, [1, 10, 20]] ];
-  var data2 = [ [1, [1, 10, 20]],
-                [2, [1, 20, 30]],
+  var data1 = [ [1, 10, [1, 20]] ];
+  var data2 = [ [1, 10, [1, 20]],
+                [2, 20, [1, 30]],
               ];
 
   var graph = document.getElementById("graph");
-  var g = new Dygraph(graph, data1, opts);
+  var g = new Dygraph(graph, data2, opts);
 
-  var rolled1 = g.rollingAverage(data1, 1);
-  var rolled2 = g.rollingAverage(data2, 1);
+  var rolled1 = g.dataHandler_.rollingAverage(data1, 1, g);
+  var rolled2 = g.dataHandler_.rollingAverage(data2, 1, g);
 
   assertEquals(rolled1[0], rolled2[0]);
 };
+
+rollingAverageTestCase.prototype.testRollCustomBars = function() {
+  var opts = {
+    customBars: true,
+    rollPeriod: 2,
+    labels: ['x', 'A']
+  };
+  var data = [ [1, [1, 10, 20]],
+               [2, [1, 20, 30]],
+               [3, [1, 30, 40]],
+               [4, [1, 40, 50]]
+              ];
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+  var rolled = this.getRolledData(g, data, 1, 2);
+  assertEquals([1, 10, [1, 20]], rolled[0]);
+  assertEquals([2, 15, [1, 25]], rolled[1]);
+  assertEquals([3, 25, [1, 35]], rolled[2]);
+  assertEquals([4, 35, [1, 45]], rolled[3]);
+};
+
+rollingAverageTestCase.prototype.testRollErrorBars = function() {
+  var opts = {
+    errorBars: true,
+    rollPeriod: 2,
+    labels: ['x', 'A']
+  };
+  var data = [ [1, [10, 1]],
+               [2, [20, 1]],
+               [3, [30, 1]],
+               [4, [40, 1]]
+              ];
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+  var rolled = this.getRolledData(g, data, 1, 2);
+  assertEquals([1, 10, [8, 12]], rolled[0]);
+  // variance = sqrt( pow(error) * rollPeriod)
+  var variance = Math.sqrt(2);
+  for (var i=1;i<data.length;i++) {
+    var value = data[i][1][0] - 5;
+    assertEquals("unexpected rolled average", value, rolled[i][1]);
+    assertEquals("unexpected rolled min", value - variance, rolled[i][2][0]);
+    assertEquals("unexpected rolled max", value + variance, rolled[i][2][1]);
+  }
+};
+
+rollingAverageTestCase.prototype.testRollFractions = function() {
+  var opts = {
+    fractions: true,
+    rollPeriod: 2,
+    labels: ['x', 'A']
+  };
+  var data = [ [1, [1, 10]],
+               [2, [2, 10]],
+               [3, [3, 10]],
+               [4, [4, 10]]
+              ];
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+  var rolled = this.getRolledData(g, data, 1, 2);
+  assertEquals([1, 10], rolled[0]);
+  assertEquals([2, 15], rolled[1]);
+  assertEquals([3, 25], rolled[2]);
+  assertEquals([4, 35], rolled[3]);
+};
+
+rollingAverageTestCase.prototype.testRollFractionsBars = function() {
+  var opts = {
+    fractions: true,
+    errorBars: true,
+    wilsonInterval: false,
+    rollPeriod: 2,
+    labels: ['x', 'A']
+  };
+  var data = [ [1, [1, 10]],
+               [2, [2, 10]],
+               [3, [3, 10]],
+               [4, [4, 10]]
+              ];
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+  var rolled = this.getRolledData(g, data, 1, 2);
+
+  // precalculated rounded values expected
+  var values = [10, 15, 25, 35];
+  var lows = [-9, -1, 6, 14];
+  var highs = [29, 31, 44, 56];
+
+  for (var i=0;i<data.length;i++) {
+    assertEquals("unexpected rolled average", values[i], Math.round(rolled[i][1]));
+    assertEquals("unexpected rolled min", lows[i], Math.round(rolled[i][2][0]));
+    assertEquals("unexpected rolled max", highs[i], Math.round(rolled[i][2][1]));
+  }
+};
+
+rollingAverageTestCase.prototype.testRollFractionsBarsWilson = function() {
+  var opts = {
+    fractions: true,
+    errorBars: true,
+    wilsonInterval: true,
+    rollPeriod: 2,
+    labels: ['x', 'A']
+  };
+  var data = [ [1, [1, 10]],
+               [2, [2, 10]],
+               [3, [3, 10]],
+               [4, [4, 10]]
+              ];
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+  var rolled = this.getRolledData(g, data, 1, 2);
+
+  //precalculated rounded values expected
+  var values = [10, 15, 25, 35];
+  var lows = [2, 5, 11, 18];
+  var highs = [41, 37, 47, 57];
+
+  for (var i=0;i<data.length;i++) {
+    assertEquals("unexpected rolled average", values[i], Math.round(rolled[i][1]));
+    assertEquals("unexpected rolled min", lows[i], Math.round(rolled[i][2][0]));
+    assertEquals("unexpected rolled max", highs[i], Math.round(rolled[i][2][1]));
+  }
+};
+
+rollingAverageTestCase.prototype.getRolledData = function(g, data, seriesIdx, rollPeriod){
+  var options = g.attributes_;
+  return g.dataHandler_.rollingAverage(g.dataHandler_.extractSeries(data, seriesIdx, options), rollPeriod, options);
+};
index 392ede4..ae77622 100644 (file)
@@ -30,6 +30,15 @@ Plugins:
 - plugins/legend.js
 - plugins/range-selector.js
 
+Datahandler:
+- datahandler/bars-custom.js
+- datahandler/bars-error.js
+- datahandler/bars-fractions.js
+- datahandler/bars.js
+- datahandler/datahandler.js
+- datahandler/default-fractions.js
+- datahandler/default.js
+
 Here's a command that can be used to build dygraphs using the closure
 compiler:
 java -jar ../../closure-compiler-read-only/build/compiler.jar --js=dygraph-utils.js --js=dashed-canvas.js --js=dygraph-options-reference.js --js=dygraph-tickers.js --js=dygraph-gviz.js --js=dygraph-options.js --js_output_file=/tmp/out.js --compilation_level ADVANCED_OPTIMIZATIONS --warning_level VERBOSE --externs dygraph-externs.js
diff --git a/datahandler/bars-custom.js b/datahandler/bars-custom.js
new file mode 100644 (file)
index 0000000..e77d746
--- /dev/null
@@ -0,0 +1,93 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler implementation for the custom bars option.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  "use strict";
+
+  var CustomBarsHandler = Dygraph.DataHandler();
+  CustomBarsHandler.prototype = Dygraph.DataHandlers.createHandler("bars");
+  Dygraph.DataHandlers.registerHandler("bars-custom", CustomBarsHandler);
+  // customBars
+  CustomBarsHandler.prototype.extractSeries = function(rawData, i, options) {
+    // TODO(danvk): pre-allocate series here.
+    var series = [];
+    var x, y, point;
+    var logScale = options.get('logscale');
+    for ( var j = 0; j < rawData.length; j++) {
+      x = rawData[j][0];
+      point = rawData[j][i];
+      if (logScale && point !== null) {
+        // On the log scale, points less than zero do not exist.
+        // This will create a gap in the chart.
+        if (point[0] <= 0 || point[1] <= 0 || point[2] <= 0) {
+          point = null;
+        }
+      }
+      // Extract to the unified data format.
+      if (point !== null) {
+        y = point[1];
+        if (y !== null && !isNaN(y)) {
+          series.push([ x, y, [ point[0], point[2] ] ]);
+        } else {
+          series.push([ x, y, [ y, y ] ]);
+        }
+      } else {
+        series.push([ x, null, [ null, null ] ]);
+      }
+    }
+    return series;
+  };
+
+  CustomBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod,
+      options) {
+    rollPeriod = Math.min(rollPeriod, originalData.length);
+    var rollingData = [];
+    var y, low, high, mid,count, i, extremes;
+
+    low = 0;
+    mid = 0;
+    high = 0;
+    count = 0;
+    for (i = 0; i < originalData.length; i++) {
+      y = originalData[i][1];
+      extremes = originalData[i][2];
+      rollingData[i] = originalData[i];
+
+      if (y !== null && !isNaN(y)) {
+        low += extremes[0];
+        mid += y;
+        high += extremes[1];
+        count += 1;
+      }
+      if (i - rollPeriod >= 0) {
+        var prev = originalData[i - rollPeriod];
+        if (prev[1] !== null && !isNaN(prev[1])) {
+          low -= prev[2][0];
+          mid -= prev[1];
+          high -= prev[2][1];
+          count -= 1;
+        }
+      }
+      if (count) {
+        rollingData[i] = [
+            originalData[i][0],
+            1.0 * mid / count, 
+            [ 1.0 * low / count,
+              1.0 * high / count ] ];
+      } else {
+        rollingData[i] = [ originalData[i][0], null, [ null, null ] ];
+      }
+    }
+
+    return rollingData;
+  };
+})();
diff --git a/datahandler/bars-error.js b/datahandler/bars-error.js
new file mode 100644 (file)
index 0000000..ff4ba92
--- /dev/null
@@ -0,0 +1,92 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler implementation for the error bars option.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  "use strict";
+
+  var ErrorBarsHandler = Dygraph.DataHandler();
+  ErrorBarsHandler.prototype = Dygraph.DataHandlers.createHandler("bars");
+  Dygraph.DataHandlers.registerHandler("bars-error", ErrorBarsHandler);
+  // errorBars
+  ErrorBarsHandler.prototype.extractSeries = function(rawData, i, options) {
+    // TODO(danvk): pre-allocate series here.
+    var series = [];
+    var x, y, variance, point;
+    var sigma = options.get("sigma");
+    var logScale = options.get('logscale');
+    for ( var j = 0; j < rawData.length; j++) {
+      x = rawData[j][0];
+      point = rawData[j][i];
+      if (logScale && point !== null) {
+        // On the log scale, points less than zero do not exist.
+        // This will create a gap in the chart.
+        if (point[0] <= 0 || point[0] - sigma * point[1] <= 0) {
+          point = null;
+        }
+      }
+      // Extract to the unified data format.
+      if (point !== null) {
+        y = point[0];
+        if (y !== null && !isNaN(y)) {
+          variance = sigma * point[1];
+          // preserve original error value in extras for further
+          // filtering
+          series.push([ x, y, [ y - variance, y + variance, point[1] ] ]);
+        } else {
+          series.push([ x, y, [ y, y, y ] ]);
+        }
+      } else {
+        series.push([ x, null, [ null, null, null ] ]);
+      }
+    }
+    return series;
+  };
+
+  ErrorBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod,
+      options) {
+    rollPeriod = Math.min(rollPeriod, originalData.length);
+    var rollingData = [];
+    var sigma = options.get("sigma");
+
+    var i, j, y, v, sum, num_ok, stddev, variance, value;
+
+    // Calculate the rolling average for the first rollPeriod - 1 points
+    // where there is not enough data to roll over the full number of points
+    for (i = 0; i < originalData.length; i++) {
+      sum = 0;
+      variance = 0;
+      num_ok = 0;
+      for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+        y = originalData[j][1];
+        if (y === null || isNaN(y))
+          continue;
+        num_ok++;
+        sum += y;
+        variance += Math.pow(originalData[j][2][2], 2);
+      }
+      if (num_ok) {
+        stddev = Math.sqrt(variance) / num_ok;
+        value = sum / num_ok;
+        rollingData[i] = [ originalData[i][0], value,
+            [value - sigma * stddev, value + sigma * stddev] ];
+      } else {
+        // This explicitly preserves NaNs to aid with "independent
+        // series".
+        // See testRollingAveragePreservesNaNs.
+        v = (rollPeriod == 1) ? originalData[i][1] : null;
+        rollingData[i] = [ originalData[i][0], v, [ v, v ] ];
+      }
+    }
+
+    return rollingData;
+  };
+})();
diff --git a/datahandler/bars-fractions.js b/datahandler/bars-fractions.js
new file mode 100644 (file)
index 0000000..88c5ca6
--- /dev/null
@@ -0,0 +1,103 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler implementation for the combination 
+ * of error bars and fractions options.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  "use strict";
+
+  var FractionsBarsHandler = Dygraph.DataHandler();
+  FractionsBarsHandler.prototype = Dygraph.DataHandlers.createHandler("bars");
+  Dygraph.DataHandlers.registerHandler("bars-fractions", FractionsBarsHandler);
+  // errorBars
+  FractionsBarsHandler.prototype.extractSeries = function(rawData, i, options) {
+    // TODO(danvk): pre-allocate series here.
+    var series = [];
+    var x, y, point, num, den, value, stddev, variance;
+    var mult = 100.0;
+    var sigma = options.get("sigma");
+    var logScale = options.get('logscale');
+    for ( var j = 0; j < rawData.length; j++) {
+      x = rawData[j][0];
+      point = rawData[j][i];
+      if (logScale && point !== null) {
+        // On the log scale, points less than zero do not exist.
+        // This will create a gap in the chart.
+        if (point[0] <= 0 || point[1] <= 0) {
+          point = null;
+        }
+      }
+      // Extract to the unified data format.
+      if (point !== null) {
+        num = point[0];
+        den = point[1];
+        if (num !== null && !isNaN(num)) {
+          value = den ? num / den : 0.0;
+          stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
+          variance = mult * stddev;
+          y = mult * value;
+          // preserve original values in extras for further filtering
+          series.push([ x, y, [ y - variance, y + variance, num, den ] ]);
+        } else {
+          series.push([ x, num, [ num, num, num, den ] ]);
+        }
+      } else {
+        series.push([ x, null, [ null, null, null, null ] ]);
+      }
+    }
+    return series;
+  };
+
+  FractionsBarsHandler.prototype.rollingAverage = function(originalData, rollPeriod,
+      options) {
+    rollPeriod = Math.min(rollPeriod, originalData.length);
+    var rollingData = [];
+    var sigma = options.get("sigma");
+    var wilsonInterval = options.get("wilsonInterval");
+
+    var low, high, i, stddev;
+    var num = 0;
+    var den = 0; // numerator/denominator
+    var mult = 100.0;
+    for (i = 0; i < originalData.length; i++) {
+      num += originalData[i][2][2];
+      den += originalData[i][2][3];
+      if (i - rollPeriod >= 0) {
+        num -= originalData[i - rollPeriod][2][2];
+        den -= originalData[i - rollPeriod][2][3];
+      }
+
+      var date = originalData[i][0];
+      var value = den ? num / den : 0.0;
+      if (wilsonInterval) {
+        // For more details on this confidence interval, see:
+        // http://en.wikipedia.org/wiki/Binomial_confidence_interval
+        if (den) {
+          var p = value < 0 ? 0 : value, n = den;
+          var pm = sigma * Math.sqrt(p * (1 - p) / n + sigma * sigma / (4 * n * n));
+          var denom = 1 + sigma * sigma / den;
+          low = (p + sigma * sigma / (2 * den) - pm) / denom;
+          high = (p + sigma * sigma / (2 * den) + pm) / denom;
+          rollingData[i] = [ date, p * mult,
+              [ low * mult, high * mult ] ];
+        } else {
+          rollingData[i] = [ date, 0, [ 0, 0 ] ];
+        }
+      } else {
+        stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
+        rollingData[i] = [ date, mult * value, 
+                           [ mult * (value - stddev), mult * (value + stddev) ] ];
+      }
+    }
+
+    return rollingData;
+  };
+})();
diff --git a/datahandler/bars.js b/datahandler/bars.js
new file mode 100644 (file)
index 0000000..5b84b69
--- /dev/null
@@ -0,0 +1,74 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler base implementation for the "bar" 
+ * data formats. This implementation must be extended and the
+ * extractSeries and rollingAverage must be implemented.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  /*global DygraphLayout:false */
+  "use strict";
+
+  var BarsHandler = Dygraph.DataHandler();
+  Dygraph.DataHandlers.registerHandler("bars", BarsHandler);
+  // errorBars
+  BarsHandler.prototype.extractSeries = function(rawData, i, options) {
+    // Not implemented here must be extended
+  };
+
+  BarsHandler.prototype.rollingAverage =  function(originalData, rollPeriod,
+      options) {
+    // Not implemented here, must be extended.
+  };
+
+  BarsHandler.prototype.onPointsCreated_ = function(series, points) {
+    for (var i = 0; i < series.length; ++i) {
+      var item = series[i];
+      var point = points[i];
+      point.y_top = NaN;
+      point.y_bottom = NaN;
+      point.yval_minus = DygraphLayout.parseFloat_(item[2][0]);
+      point.yval_plus = DygraphLayout.parseFloat_(item[2][1]);
+    }
+  };
+
+  BarsHandler.prototype.getExtremeYValues = function(series, dateWindow, options) {
+    var minY = null, maxY = null, y;
+
+    var firstIdx = 0;
+    var lastIdx = series.length - 1;
+
+    for ( var j = firstIdx; j <= lastIdx; j++) {
+      y = series[j][1];
+      if (y === null || isNaN(y)) continue;
+
+      var low = series[j][2][0];
+      var high = series[j][2][1];
+
+      if (low > y) low = y; // this can happen with custom bars,
+      if (high < y) high = y; // e.g. in tests/custom-bars.html
+
+      if (maxY === null || high > maxY) maxY = high;
+      if (minY === null || low < minY) minY = low;
+    }
+
+    return [ minY, maxY ];
+  };
+
+  BarsHandler.prototype.onLineEvaluated = function(points, axis, logscale) {
+    var point;
+    for (var j = 0; j < points.length; j++) {
+      // Copy over the error terms
+      point = points[j];
+      point.y_top = DygraphLayout._calcYNormal(axis, point.yval_minus, logscale);
+      point.y_bottom = DygraphLayout._calcYNormal(axis, point.yval_plus, logscale);
+    }
+  };
+})();
diff --git a/datahandler/datahandler.js b/datahandler/datahandler.js
new file mode 100644 (file)
index 0000000..d08f7a7
--- /dev/null
@@ -0,0 +1,305 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview This file contains the managment of data handlers
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * 
+ * The idea is to define a common, generic data format that works for all data
+ * structures supported by dygraphs. To make this possible, the DataHandler
+ * interface is introduced. This makes it possible, that dygraph itself can work
+ * with the same logic for every data type independent of the actual format and
+ * the DataHandler takes care of the data format specific jobs. 
+ * DataHandlers are implemented for all data types supported by Dygraphs and
+ * return Dygraphs compliant formats.
+ * By default the correct DataHandler is chosen based on the options set.
+ * Optionally the user may use his own DataHandler (similar to the plugin
+ * system).
+ * 
+ * 
+ * The unified data format returend by each handler is defined as so: 
+ * series[n][point] = [x,y,(extras)] 
+ * 
+ * This format contains the common basis that is needed to draw a simple line
+ * series extended by optional extras for more complex graphing types. It
+ * contains a primitive x value as first array entry, a primitive y value as
+ * second array entry and an optional extras object for additional data needed.
+ * 
+ * x must always be a number.
+ * y must always be a number, NaN of type number or null.
+ * extras is optional and must be interpreted by the DataHandler. It may be of
+ * any type. 
+ * 
+ * In practice this might look something like this:
+ * default: [x, yVal]
+ * errorBar / customBar: [x, yVal, [yTopVariance, yBottomVariance] ]
+ * 
+ */
+/*jshint globalstrict: true */
+/*global Dygraph:false */
+/*global DygraphLayout:false */
+"use strict";
+
+/**
+ * A collection of functions to create and retrieve data handlers.
+ */
+Dygraph.DataHandlers = {};
+
+/**
+ * All registered data handlers are stored here.
+ * 
+ * @private
+ */
+Dygraph.DataHandlers.handlers_ = {};
+
+/**
+ * @param name {!string} The name the data handler should be registered to.
+ *          Registers a data handler by the given name and makes it publicly
+ *          accessible.
+ * @param handler {!Dygraph.DataHandler} DataHandler implementation which must be an
+ *          instance of Dygraph.DataHandler.
+ * @public
+ */
+Dygraph.DataHandlers.registerHandler = function(name, handler) {
+  if (!handler instanceof Dygraph.DataHandler) {
+    throw ("the handler must be a prototype of Dygraph.DataHandler");
+  }
+  Dygraph.DataHandlers.handlers_[name] = handler;
+};
+
+/**
+ * Returns the data handler registered to the given name.
+ * Note this is the data handler constructor method.
+ * 
+ * @param name {!string} The name, the handler was registered to.
+ * @returns {Dygraph.DataHandler} The data handler constructor.
+ * @public
+ */
+Dygraph.DataHandlers.getHandler = function(name) {
+  return Dygraph.DataHandlers.handlers_[name];
+};
+
+/**
+ * Returns the cunstructed data handler registered to the given name.
+ * 
+ * @param name {!string} The name, the handler was registered to.
+ * @returns {Dygraph.DataHandler} A constructed instance of the data handler.
+ * @public
+ */
+Dygraph.DataHandlers.createHandler = function(name) {
+  return new Dygraph.DataHandlers.handlers_[name]();
+};
+
+/**
+ * 
+ * The data handler is responsible for all data specific operations. All of the
+ * series data it receives and returns is always in the unified data format.
+ * Initially the unified data is created by the extractSeries method
+ * 
+ * @class
+ */
+Dygraph.DataHandler = function () {
+  /**
+   * Constructor for all data handlers.
+   * @constructor
+   */
+  var handler = function() {
+    return this;
+  };
+
+  /**
+   * X-value array index constant for unified data samples.
+   * @const
+   * @type {number}
+   */
+  handler.X = 0;
+
+  /**
+   * Y-value array index constant for unified data samples.
+   * @const
+   * @type {number}
+   */
+  handler.Y = 1;
+
+  /**
+   * Extras-value array index constant for unified data samples.
+   * @const
+   * @type {number}
+   */
+  handler.EXTRAS = 2;
+
+  /**
+   * Extracts one series from the raw data (a 2D array) into an array of the
+   * unified data format.
+   * This is where undesirable points (i.e. negative values on log scales and
+   * missing values through which we wish to connect lines) are dropped.
+   * TODO(danvk): the "missing values" bit above doesn't seem right.
+   * 
+   * @param rawData {!Array.<Array>} The raw data passed into dygraphs where 
+   *          rawData[i] = [x,ySeries1,...,ySeriesN].
+   * @param seriesIndex {!number} Index of the series to extract. All other series should
+   *          be ignored.
+   * @param options {!DygraphOptions} Dygraph options.
+   * @returns {Array.<[!number,?number,?]>} The series in the unified data format
+   *          where series[i] = [x,y,{extras}]. 
+   * @public
+   */
+  handler.prototype.extractSeries = function(rawData, seriesIndex, options) {
+  };
+
+  /**
+   * Converts a series to a Point array.
+   * 
+   * @param {!Array.<[!number,?number,?]>} series The series in the unified 
+   *          data format where series[i] = [x,y,{extras}].
+   * @param {!string} setName Name of the series.
+   * @param {!number} boundaryIdStart Index offset of the first point, equal to the
+   *          number of skipped points left of the date window minimum (if any).
+   * @return {!Array.<Dygraph.PointType>} List of points for this series.
+   * @public
+   */
+  handler.prototype.seriesToPoints = function(series, setName, boundaryIdStart) {
+    // TODO(bhs): these loops are a hot-spot for high-point-count charts. In
+    // fact,
+    // on chrome+linux, they are 6 times more expensive than iterating through
+    // the
+    // points and drawing the lines. The brunt of the cost comes from allocating
+    // the |point| structures.
+    var points = [];
+    for ( var i = 0; i < series.length; ++i) {
+      var item = series[i];
+      var yraw = item[1];
+      var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw);
+      var point = {
+        x : NaN,
+        y : NaN,
+        xval : DygraphLayout.parseFloat_(item[0]),
+        yval : yval,
+        name : setName, // TODO(danvk): is this really necessary?
+        idx : i + boundaryIdStart
+      };
+      points.push(point);
+    }
+    handler.prototype.onPointsCreated_(series, points);
+    return points;
+  };
+
+  /**
+   * Callback called for each series after the series points have been generated
+   * which will later be used by the plotters to draw the graph.
+   * Here data may be added to the seriesPoints which is needed by the plotters.
+   * The indexes of series and points are in sync meaning the original data
+   * sample for series[i] is points[i].
+   * 
+   * @param {!Array.<[!number,?number,?]>} series The series in the unified 
+   *          data format where series[i] = [x,y,{extras}].
+   * @param {!Array.<Dygraph.PointType>} points The corresponding points passed 
+   *          to the plotter.
+   * @private
+   */
+  handler.prototype.onPointsCreated_ = function(series, points) {
+  };
+
+  /**
+   * Calculates the rolling average of a data set.
+   * 
+   * @param {!Array.<[!number,?number,?]>} series The series in the unified 
+   *          data format where series[i] = [x,y,{extras}].
+   * @param {!number} rollPeriod The number of points over which to average the data
+   * @param {!DygraphOptions} options The dygraph options.
+   * @return the rolled series.
+   * @public
+   */
+  handler.prototype.rollingAverage = function(series, rollPeriod, options) {
+  };
+
+  /**
+   * Computes the range of the data series (including confidence intervals).
+   * 
+   * @param {!Array.<[!number,?number,?]>} series The series in the unified 
+   *          data format where series[i] = [x,y,{extras}].
+   * @param {!Array.<number>} dateWindow The x-value range to display with 
+   *          the format: [min,max].
+   * @param {!DygraphOptions} options The dygraph options.
+   * @return {Array.<number>} The low and high extremes of the series in the given window with 
+   *          the format: [low, high].
+   * @public
+   */
+  handler.prototype.getExtremeYValues = function(series, dateWindow, options) {
+  };
+
+  /**
+   * Callback called for each series after the layouting data has been
+   * calculated before the series is drawn. Here normalized positioning data
+   * should be calculated for the extras of each point.
+   * 
+   * @param {!Array.<Dygraph.PointType>} points The points passed to 
+   *          the plotter.
+   * @param {!Object} axis The axis on which the series will be plotted.
+   * @param {!boolean} logscale Weather or not to use a logscale.
+   * @public
+   */
+  handler.prototype.onLineEvaluated = function(points, axis, logscale) {
+  };
+
+  /**
+   * Helper method that computes the y value of a line defined by the points p1
+   * and p2 and a given x value.
+   * 
+   * @param {!Array.<number>} p1 left point ([x,y]).
+   * @param {!Array.<number>} p2 right point ([x,y]).
+   * @param {!number} xValue The x value to compute the y-intersection for.
+   * @return {number} corresponding y value to x on the line defined by p1 and p2.
+   * @private
+   */
+  handler.prototype.computeYInterpolation_ = function(p1, p2, xValue) {
+    var deltaY = p2[1] - p1[1];
+    var deltaX = p2[0] - p1[0];
+    var gradient = deltaY / deltaX;
+    var growth = (xValue - p1[0]) * gradient;
+    return p1[1] + growth;
+  };
+
+  /**
+   * Helper method that returns the first and the last index of the given series
+   * that lie inside the given dateWindow.
+   * 
+   * @param {!Array.<[!number,?number,?]>} series The series in the unified 
+   *          data format where series[i] = [x,y,{extras}].
+   * @param {!Array.<number>} dateWindow The x-value range to display with 
+   *          the format: [min,max].
+   * @return {!Array.<[!number,?number,?]>} The samples of the series that 
+   *          are in the given date window.
+   * @private
+   */
+  handler.prototype.getIndexesInWindow_ = function(series, dateWindow) {
+    var firstIdx = 0, lastIdx = series.length - 1;
+    if (dateWindow) {
+      var idx = 0;
+      var low = dateWindow[0];
+      var high = dateWindow[1];
+
+      // Start from each side of the array to minimize the performance
+      // needed.
+      while (idx < series.length - 1 && series[idx][0] < low) {
+        firstIdx++;
+        idx++;
+      }
+      idx = series.length - 1;
+      while (idx > 0 && series[idx][0] > high) {
+        lastIdx--;
+        idx--;
+      }
+    }
+    if (firstIdx <= lastIdx) {
+      return [ firstIdx, lastIdx ];
+    } else {
+      return [ 0, series.length - 1 ];
+    }
+  };
+
+  return handler;
+};
diff --git a/datahandler/default-fractions.js b/datahandler/default-fractions.js
new file mode 100644 (file)
index 0000000..7e234b0
--- /dev/null
@@ -0,0 +1,79 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler implementation for the fractions option.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  "use strict";
+
+  var DefaultFractionHandler = Dygraph.DataHandler();
+  DefaultFractionHandler.prototype = Dygraph.DataHandlers.createHandler("default");
+  Dygraph.DataHandlers.registerHandler("default-fractions", DefaultFractionHandler);
+
+  DefaultFractionHandler.prototype.extractSeries = function(rawData, i, options) {
+    // TODO(danvk): pre-allocate series here.
+    var series = [];
+    var x, y, point, num, den, value;
+    var mult = 100.0;
+    var logScale = options.get('logscale');
+    for ( var j = 0; j < rawData.length; j++) {
+      x = rawData[j][0];
+      point = rawData[j][i];
+      if (logScale && point !== null) {
+        // On the log scale, points less than zero do not exist.
+        // This will create a gap in the chart.
+        if (point[0] <= 0 || point[1] <= 0) {
+          point = null;
+        }
+      }
+      // Extract to the unified data format.
+      if (point !== null) {
+        num = point[0];
+        den = point[1];
+        if (num !== null && !isNaN(num)) {
+          value = den ? num / den : 0.0;
+          y = mult * value;
+          // preserve original values in extras for further filtering
+          series.push([ x, y, [ num, den ] ]);
+        } else {
+          series.push([ x, num, [ num, den ] ]);
+        }
+      } else {
+        series.push([ x, null, [ null, null ] ]);
+      }
+    }
+    return series;
+  };
+
+  DefaultFractionHandler.prototype.rollingAverage = function(originalData, rollPeriod,
+      options) {
+    rollPeriod = Math.min(rollPeriod, originalData.length);
+    var rollingData = [];
+
+    var i;
+    var num = 0;
+    var den = 0; // numerator/denominator
+    var mult = 100.0;
+    for (i = 0; i < originalData.length; i++) {
+      num += originalData[i][2][0];
+      den += originalData[i][2][1];
+      if (i - rollPeriod >= 0) {
+        num -= originalData[i - rollPeriod][2][0];
+        den -= originalData[i - rollPeriod][2][1];
+      }
+
+      var date = originalData[i][0];
+      var value = den ? num / den : 0.0;
+      rollingData[i] = [ date, mult * value ];
+    }
+
+    return rollingData;
+  };
+})();
diff --git a/datahandler/default.js b/datahandler/default.js
new file mode 100644 (file)
index 0000000..79adcb5
--- /dev/null
@@ -0,0 +1,88 @@
+/**
+ * @license
+ * Copyright 2013 David Eberlein (david.eberlein@ch.sauter-bc.com)
+ * MIT-licensed (http://opensource.org/licenses/MIT)
+ */
+
+/**
+ * @fileoverview DataHandler default implementation used for simple line charts.
+ * @author David Eberlein (david.eberlein@ch.sauter-bc.com)
+ */
+
+(function() {
+  /*global Dygraph:false */
+  "use strict";
+
+  var DefaultHandler = Dygraph.DataHandler();
+  Dygraph.DataHandlers.registerHandler("default", DefaultHandler);
+
+  DefaultHandler.prototype.extractSeries = function(rawData, i, options) {
+    // TODO(danvk): pre-allocate series here.
+    var series = [];
+    var logScale = options.get('logscale');
+    for ( var j = 0; j < rawData.length; j++) {
+      var x = rawData[j][0];
+      var point = rawData[j][i];
+      if (logScale) {
+        // On the log scale, points less than zero do not exist.
+        // This will create a gap in the chart.
+        if (point <= 0) {
+          point = null;
+        }
+      }
+      series.push([ x, point ]);
+    }
+    return series;
+  };
+
+  DefaultHandler.prototype.rollingAverage = function(originalData, rollPeriod,
+      options) {
+    rollPeriod = Math.min(rollPeriod, originalData.length);
+    var rollingData = [];
+
+    var i, j, y, sum, num_ok;
+    // Calculate the rolling average for the first rollPeriod - 1 points
+    // where
+    // there is not enough data to roll over the full number of points
+    if (rollPeriod == 1) {
+      return originalData;
+    }
+    for (i = 0; i < originalData.length; i++) {
+      sum = 0;
+      num_ok = 0;
+      for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
+        y = originalData[j][1];
+        if (y === null || isNaN(y))
+          continue;
+        num_ok++;
+        sum += originalData[j][1];
+      }
+      if (num_ok) {
+        rollingData[i] = [ originalData[i][0], sum / num_ok ];
+      } else {
+        rollingData[i] = [ originalData[i][0], null ];
+      }
+    }
+
+    return rollingData;
+  };
+
+  DefaultHandler.prototype.getExtremeYValues = function(series, dateWindow,
+      options) {
+    var minY = null, maxY = null, y;
+    var firstIdx = 0, lastIdx = series.length - 1;
+
+    for ( var j = firstIdx; j <= lastIdx; j++) {
+      y = series[j][1];
+      if (y === null || isNaN(y))
+        continue;
+      if (maxY === null || y > maxY) {
+        maxY = y;
+      }
+      if (minY === null || y < minY) {
+        minY = y;
+      }
+    }
+    return [ minY, maxY ];
+  };
+})();
index c06d23d..160a01e 100644 (file)
     "plugins/legend.js",
     "plugins/range-selector.js",
     "dygraph-plugin-install.js",
-    "dygraph-options-reference.js"  // Shouldn't be included in generate-combined.sh
+    "dygraph-options-reference.js",  // Shouldn't be included in generate-combined.sh
+    "datahandler/datahandler.js",
+    "datahandler/default.js",
+    "datahandler/default-fractions.js",
+    "datahandler/bars.js",
+    "datahandler/bars-error.js",
+    "datahandler/bars-custom.js",
+    "datahandler/bars-fractions.js"
   ];
 
   for (var i = 0; i < source_files.length; i++) {
index e766e8f..93e0c3a 100644 (file)
@@ -222,7 +222,6 @@ DygraphLayout._calcYNormal = function(axis, value, logscale) {
 DygraphLayout.prototype._evaluateLineCharts = function() {
   var connectSeparated = this.attr_('connectSeparatedPoints');
   var isStacked = this.attr_("stackedGraph");
-  var hasBars = this.attr_('errorBars') || this.attr_('customBars');
 
   for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
     var points = this.points[setIdx];
@@ -252,14 +251,9 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
         }
       }
       point.y = DygraphLayout._calcYNormal(axis, yval, logscale);
-
-      if (hasBars) {
-        point.y_top = DygraphLayout._calcYNormal(
-            axis, yval - point.yval_minus, logscale);
-        point.y_bottom = DygraphLayout._calcYNormal(
-            axis, yval + point.yval_plus, logscale);
-      }
     }
+
+    this.dygraph_.dataHandler_.onLineEvaluated(points, axis, logscale);
   }
 };
 
index f05ac47..48475ef 100644 (file)
@@ -2154,45 +2154,28 @@ Dygraph.prototype.addXTicks_ = function() {
 
 /**
  * @private
- * Computes the range of the data series (including confidence intervals).
- * @param { [Array] } series either [ [x1, y1], [x2, y2], ... ] or
- * [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
- * @return [low, high]
- */
-Dygraph.prototype.extremeValues_ = function(series) {
-  var minY = null, maxY = null, j, y;
-
-  var bars = this.attr_("errorBars") || this.attr_("customBars");
-  if (bars) {
-    // With custom bars, maxY is the max of the high values.
-    for (j = 0; j < series.length; j++) {
-      y = series[j][1][0];
-      if (y === null || isNaN(y)) continue;
-      var low = y - series[j][1][1];
-      var high = y + series[j][1][2];
-      if (low > y) low = y;    // this can happen with custom bars,
-      if (high < y) high = y;  // e.g. in tests/custom-bars.html
-      if (maxY === null || high > maxY) {
-        maxY = high;
-      }
-      if (minY === null || low < minY) {
-        minY = low;
-      }
+ * Returns the correct handler ID for the currently set options. 
+ * The actual handler may then be retrieved using the
+ * Dygraph.DataHandlers.getHandler() method.
+ */
+Dygraph.prototype.getHandlerId_ = function() {
+  var handlerId;
+  if (this.attr_("dataHandlerId")) {
+    handlerId =  this.attr_("dataHandlerId");
+  } else if (this.fractions_){
+    if (this.attr_("errorBars")) {
+      handlerId = "bars-fractions";
+    } else {
+      handlerId = "default-fractions";
     }
+  } else if (this.attr_("customBars")) {
+    handlerId = "bars-custom";
+  } else if (this.attr_("errorBars")) {
+    handlerId = "bars-error";
   } else {
-    for (j = 0; j < series.length; j++) {
-      y = series[j][1];
-      if (y === null || isNaN(y)) continue;
-      if (maxY === null || y > maxY) {
-        maxY = y;
-      }
-      if (minY === null || y < minY) {
-        minY = y;
-      }
-    }
+    handlerId = "default";
   }
-
-  return [minY, maxY];
+  return handlerId;
 };
 
 /**
@@ -2205,6 +2188,9 @@ Dygraph.prototype.extremeValues_ = function(series) {
  */
 Dygraph.prototype.predraw_ = function() {
   var start = new Date();
+  
+  // Create the correct dataHandler
+  this.dataHandler_ = new (Dygraph.DataHandlers.getHandler(this.getHandlerId_()))();
 
   this.layout_.computePlotArea();
 
@@ -2241,9 +2227,11 @@ Dygraph.prototype.predraw_ = function() {
   this.rolledSeries_ = [null];  // x-axis is the first series and it's special
   for (var i = 1; i < this.numColumns(); i++) {
     // var logScale = this.attr_('logscale', i); // TODO(klausw): this looks wrong // konigsberg thinks so too.
-    var logScale = this.attr_('logscale');
-    var series = this.extractSeries_(this.rawData_, i, logScale);
-    series = this.rollingAverage(series, this.rollPeriod_);
+    var series = this.dataHandler_.extractSeries(this.rawData_, i, this.attributes_);
+    if (this.rollPeriod_ > 1) {
+      series = this.dataHandler_.rollingAverage(series, this.rollPeriod_, this.attributes_);
+    }
+    
     this.rolledSeries_.push(series);
   }
 
@@ -2280,49 +2268,6 @@ Dygraph.prototype.predraw_ = function() {
  */
 Dygraph.PointType = undefined;
 
-// TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact,
-// on chrome+linux, they are 6 times more expensive than iterating through the
-// points and drawing the lines. The brunt of the cost comes from allocating
-// the |point| structures.
-/**
- * Converts a series to a Point array.
- *
- * @private
- * @param {Array.<Array.<(?number|Array<?number>)>} series Array where
- *     series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]].
- * @param {boolean} bars True if error bars or custom bars are being drawn.
- * @param {string} setName Name of the series.
- * @param {number} boundaryIdStart Index offset of the first point, equal to
- *     the number of skipped points left of the date window minimum (if any).
- * @return {Array.<Dygraph.PointType>} List of points for this series.
- */
-Dygraph.seriesToPoints_ = function(series, bars, setName, boundaryIdStart) {
-  var points = [];
-  for (var i = 0; i < series.length; ++i) {
-    var item = series[i];
-    var yraw = bars ? item[1][0] : item[1];
-    var yval = yraw === null ? null : DygraphLayout.parseFloat_(yraw);
-    var point = {
-      x: NaN,
-      y: NaN,
-      xval: DygraphLayout.parseFloat_(item[0]),
-      yval: yval,
-      name: setName,  // TODO(danvk): is this really necessary?
-      idx: i + boundaryIdStart
-    };
-
-    if (bars) {
-      point.y_top = NaN;
-      point.y_bottom = NaN;
-      point.yval_minus = DygraphLayout.parseFloat_(item[1][1]);
-      point.yval_plus = DygraphLayout.parseFloat_(item[1][2]);
-    }
-    points.push(point);
-  }
-  return points;
-};
-
-
 /**
  * Calculates point stacking for stackedGraph=true.
  *
@@ -2438,43 +2383,34 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
   var points = [];
   var cumulativeYval = [];  // For stacked series.
   var extremes = {};  // series name -> [low, high]
-  var i, k;
-  var errorBars = this.attr_("errorBars");
-  var customBars = this.attr_("customBars");
-  var bars = errorBars || customBars;
-  var isValueNull = function(sample) {
-    if (!bars) {
-      return sample[1] === null;
-    } else {
-      return customBars ? sample[1][1] === null : 
-        errorBars ? sample[1][0] === null : false;
-    }
-  };
-
+  var seriesIdx, sampleIdx;
+  var firstIdx, lastIdx;
+  
   // Loop over the fields (series).  Go from the last to the first,
   // because if they're stacked that's how we accumulate the values.
   var num_series = rolledSeries.length - 1;
   var series;
-  for (i = num_series; i >= 1; i--) {
-    if (!this.visibility()[i - 1]) continue;
+  for (seriesIdx = num_series; seriesIdx >= 1; seriesIdx--) {
+    if (!this.visibility()[seriesIdx - 1]) continue;
 
     // Prune down to the desired range, if necessary (for zooming)
     // Because there can be lines going to points outside of the visible area,
     // we actually prune to visible points, plus one on either side.
     if (dateWindow) {
-      series = rolledSeries[i];
+      series = rolledSeries[seriesIdx];
       var low = dateWindow[0];
       var high = dateWindow[1];
 
       // TODO(danvk): do binary search instead of linear search.
       // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
-      var firstIdx = null, lastIdx = null;
-      for (k = 0; k < series.length; k++) {
-        if (series[k][0] >= low && firstIdx === null) {
-          firstIdx = k;
+      firstIdx = null; 
+      lastIdx = null;
+      for (sampleIdx = 0; sampleIdx < series.length; sampleIdx++) {
+        if (series[sampleIdx][0] >= low && firstIdx === null) {
+          firstIdx = sampleIdx;
         }
-        if (series[k][0] <= high) {
-          lastIdx = k;
+        if (series[sampleIdx][0] <= high) {
+          lastIdx = sampleIdx;
         }
       }
 
@@ -2483,7 +2419,8 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
       var isInvalidValue = true;
       while (isInvalidValue && correctedFirstIdx > 0) {
         correctedFirstIdx--;
-        isInvalidValue = isValueNull(series[correctedFirstIdx]);
+        // check if the y value is null.
+        isInvalidValue = series[correctedFirstIdx][1] === null;
       }
 
       if (lastIdx === null) lastIdx = series.length - 1;
@@ -2491,10 +2428,9 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
       isInvalidValue = true;
       while (isInvalidValue && correctedLastIdx < series.length - 1) {
         correctedLastIdx++;
-        isInvalidValue = isValueNull(series[correctedLastIdx]);
+        isInvalidValue = series[correctedLastIdx][1] === null;
       }
 
-
       if (correctedFirstIdx!==firstIdx) {
         firstIdx = correctedFirstIdx;
       }
@@ -2502,20 +2438,21 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
         lastIdx = correctedLastIdx;
       }
       
-      boundaryIds[i-1] = [firstIdx, lastIdx];
+      boundaryIds[seriesIdx-1] = [firstIdx, lastIdx];
       
       // .slice's end is exclusive, we want to include lastIdx.
       series = series.slice(firstIdx, lastIdx + 1);
     } else {
-      series = rolledSeries[i];
-      boundaryIds[i-1] = [0, series.length-1];
+      series = rolledSeries[seriesIdx];
+      boundaryIds[seriesIdx-1] = [0, series.length-1];
     }
 
-    var seriesName = this.attr_("labels")[i];
-    var seriesExtremes = this.extremeValues_(series);
+    var seriesName = this.attr_("labels")[seriesIdx];
+    var seriesExtremes = this.dataHandler_.getExtremeYValues(series, 
+        dateWindow, this.attr_("stepPlot",seriesName));
 
-    var seriesPoints = Dygraph.seriesToPoints_(
-        series, bars, seriesName, boundaryIds[i-1][0]);
+    var seriesPoints = this.dataHandler_.seriesToPoints(series, 
+        seriesName, boundaryIds[seriesIdx-1][0]);
 
     if (this.attr_("stackedGraph")) {
       Dygraph.stackPoints_(seriesPoints, cumulativeYval, seriesExtremes,
@@ -2523,7 +2460,7 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
     }
 
     extremes[seriesName] = seriesExtremes;
-    points[i] = seriesPoints;
+    points[seriesIdx] = seriesPoints;
   }
 
   return { points: points, extremes: extremes, boundaryIds: boundaryIds };
@@ -2900,198 +2837,6 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
 };
 
 /**
- * Extracts one series from the raw data (a 2D array) into an array of (date,
- * value) tuples.
- *
- * This is where undesirable points (i.e. negative values on log scales and
- * missing values through which we wish to connect lines) are dropped.
- * TODO(danvk): the "missing values" bit above doesn't seem right.
- *
- * @private
- * @param {Array.<Array.<(number|Array<Number>)>>} rawData Input data. Rectangular
- *     grid of points, where rawData[row][0] is the X value for the row,
- *     and rawData[row][i] is the Y data for series #i.
- * @param {number} i Series index, starting from 1.
- * @param {boolean} logScale True if using logarithmic Y scale.
- * @return {Array.<Array.<(?number|Array<?number>)>} Series array, where
- *     series[row] = [x,y] or [x, [y, err]] or [x, [y, yplus, yminus]].
- */
-Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
-  // TODO(danvk): pre-allocate series here.
-  var series = [];
-  var errorBars = this.attr_("errorBars");
-  var customBars =  this.attr_("customBars");
-  for (var j = 0; j < rawData.length; j++) {
-    var x = rawData[j][0];
-    var point = rawData[j][i];
-    if (logScale) {
-      // On the log scale, points less than zero do not exist.
-      // This will create a gap in the chart.
-      if (errorBars || customBars) {
-        // point.length is either 2 (errorBars) or 3 (customBars)
-        for (var k = 0; k < point.length; k++) {
-          if (point[k] <= 0) {
-            point = null;
-            break;
-          }
-        }
-      } else if (point <= 0) {
-        point = null;
-      }
-    }
-    // Fix null points to fit the display type standard.
-    if (point !== null) {
-      series.push([x, point]);
-    } else {
-      series.push([x, errorBars ? [null, null] : customBars ? [null, null, null] : point]);
-    }
-  }
-  return series;
-};
-
-/**
- * @private
- * Calculates the rolling average of a data set.
- * If originalData is [label, val], rolls the average of those.
- * If originalData is [label, [, it's interpreted as [value, stddev]
- *   and the roll is returned in the same form, with appropriately reduced
- *   stddev for each value.
- * Note that this is where fractional input (i.e. '5/10') is converted into
- *   decimal values.
- * @param {Array} originalData The data in the appropriate format (see above)
- * @param {Number} rollPeriod The number of points over which to average the
- *                            data
- */
-Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
-  rollPeriod = Math.min(rollPeriod, originalData.length);
-  var rollingData = [];
-  var sigma = this.attr_("sigma");
-
-  var low, high, i, j, y, sum, num_ok, stddev;
-  if (this.fractions_) {
-    var num = 0;
-    var den = 0;  // numerator/denominator
-    var mult = 100.0;
-    for (i = 0; i < originalData.length; i++) {
-      num += originalData[i][1][0];
-      den += originalData[i][1][1];
-      if (i - rollPeriod >= 0) {
-        num -= originalData[i - rollPeriod][1][0];
-        den -= originalData[i - rollPeriod][1][1];
-      }
-
-      var date = originalData[i][0];
-      var value = den ? num / den : 0.0;
-      if (this.attr_("errorBars")) {
-        if (this.attr_("wilsonInterval")) {
-          // For more details on this confidence interval, see:
-          // http://en.wikipedia.org/wiki/Binomial_confidence_interval
-          if (den) {
-            var p = value < 0 ? 0 : value, n = den;
-            var pm = sigma * Math.sqrt(p*(1-p)/n + sigma*sigma/(4*n*n));
-            var denom = 1 + sigma * sigma / den;
-            low  = (p + sigma * sigma / (2 * den) - pm) / denom;
-            high = (p + sigma * sigma / (2 * den) + pm) / denom;
-            rollingData[i] = [date,
-                              [p * mult, (p - low) * mult, (high - p) * mult]];
-          } else {
-            rollingData[i] = [date, [0, 0, 0]];
-          }
-        } else {
-          stddev = den ? sigma * Math.sqrt(value * (1 - value) / den) : 1.0;
-          rollingData[i] = [date, [mult * value, mult * stddev, mult * stddev]];
-        }
-      } else {
-        rollingData[i] = [date, mult * value];
-      }
-    }
-  } else if (this.attr_("customBars")) {
-    low = 0;
-    var mid = 0;
-    high = 0;
-    var count = 0;
-    for (i = 0; i < originalData.length; i++) {
-      var data = originalData[i][1];
-      y = data[1];
-      rollingData[i] = [originalData[i][0], [y, y - data[0], data[2] - y]];
-
-      if (y !== null && !isNaN(y)) {
-        low += data[0];
-        mid += y;
-        high += data[2];
-        count += 1;
-      }
-      if (i - rollPeriod >= 0) {
-        var prev = originalData[i - rollPeriod];
-        if (prev[1][1] !== null && !isNaN(prev[1][1])) {
-          low -= prev[1][0];
-          mid -= prev[1][1];
-          high -= prev[1][2];
-          count -= 1;
-        }
-      }
-      if (count) {
-        rollingData[i] = [originalData[i][0], [ 1.0 * mid / count,
-                                                1.0 * (mid - low) / count,
-                                                1.0 * (high - mid) / count ]];
-      } else {
-        rollingData[i] = [originalData[i][0], [null, null, null]];
-      }
-    }
-  } else {
-    // Calculate the rolling average for the first rollPeriod - 1 points where
-    // there is not enough data to roll over the full number of points
-    if (!this.attr_("errorBars")) {
-      if (rollPeriod == 1) {
-        return originalData;
-      }
-
-      for (i = 0; i < originalData.length; i++) {
-        sum = 0;
-        num_ok = 0;
-        for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-          y = originalData[j][1];
-          if (y === null || isNaN(y)) continue;
-          num_ok++;
-          sum += originalData[j][1];
-        }
-        if (num_ok) {
-          rollingData[i] = [originalData[i][0], sum / num_ok];
-        } else {
-          rollingData[i] = [originalData[i][0], null];
-        }
-      }
-
-    } else {
-      for (i = 0; i < originalData.length; i++) {
-        sum = 0;
-        var variance = 0;
-        num_ok = 0;
-        for (j = Math.max(0, i - rollPeriod + 1); j < i + 1; j++) {
-          y = originalData[j][1][0];
-          if (y === null || isNaN(y)) continue;
-          num_ok++;
-          sum += originalData[j][1][0];
-          variance += Math.pow(originalData[j][1][1], 2);
-        }
-        if (num_ok) {
-          stddev = Math.sqrt(variance) / num_ok;
-          rollingData[i] = [originalData[i][0],
-                            [sum / num_ok, sigma * stddev, sigma * stddev]];
-        } else {
-          // This explicitly preserves NaNs to aid with "independent series".
-          // See testRollingAveragePreservesNaNs.
-          var v = (rollPeriod == 1) ? originalData[i][1][0] : null;
-          rollingData[i] = [originalData[i][0], [v, v, v]];
-        }
-      }
-    }
-  }
-
-  return rollingData;
-};
-
-/**
  * Detects the type of the str (date or numeric) and sets the various
  * formatting attributes in this.attrs_ based on this type.
  * @param {String} str An x value.
index 9e184ae..09f316f 100755 (executable)
@@ -19,7 +19,14 @@ GetSources () {
     dygraph-tickers.js \
     dygraph-plugin-base.js \
     plugins/*.js \
-    dygraph-plugin-install.js
+    dygraph-plugin-install.js \
+    datahandler/datahandler.js \
+    datahandler/default.js \
+    datahandler/default-fractions.js \
+    datahandler/bars.js \
+    datahandler/bars-custom.js \
+    datahandler/bars-error.js \
+    datahandler/bars-fractions.js 
   do
       echo "$F"
   done
index ffbc369..04f49da 100644 (file)
@@ -20,4 +20,11 @@ load:
   - dygraph-plugin-base.js
   - plugins/*.js
   - dygraph-plugin-install.js
+  - datahandler/datahandler.js
+  - datahandler/default.js
+  - datahandler/default-fractions.js
+  - datahandler/bars.js
+  - datahandler/bars-error.js
+  - datahandler/bars-custom.js
+  - datahandler/bars-fractions.js
   - auto_tests/tests/*.js
diff --git a/lint.sh b/lint.sh
index ef4dacd..eb61924 100755 (executable)
--- a/lint.sh
+++ b/lint.sh
@@ -21,7 +21,7 @@ fi
 RETURN_VALUE=0
 
 if [ $# -eq 0 ]; then
-  files=$(ls dygraph*.js plugins/*.js | grep -v combined | grep -v dev.js| grep -v externs)
+  files=$(ls dygraph*.js plugins/*.js datahandler/*.js | grep -v combined | grep -v dev.js| grep -v externs)
 else
   files=$1
 fi