sync in the massive r48
authorDan Vanderkam <danvdk@gmail.com>
Tue, 24 Nov 2009 12:17:06 +0000 (06:17 -0600)
committerDan Vanderkam <danvdk@gmail.com>
Tue, 24 Nov 2009 12:17:06 +0000 (06:17 -0600)
20 files changed:
README
docs/index.html
dygraph-canvas.js
dygraph-combined.js
dygraph.js
tests/border.html
tests/custom-bars.html [new file with mode: 0644]
tests/customLabel.html
tests/data.js
tests/demo.html [new file with mode: 0644]
tests/dygraph.html [new file with mode: 0644]
tests/grid_dot.html
tests/gviz.html
tests/hourly.html
tests/label-div.html [new file with mode: 0644]
tests/native-format.html [new file with mode: 0644]
tests/noise.html
tests/numeric-axis.html
tests/spacing.html
tests/two-series.html

diff --git a/README b/README
index abb225c..7d38152 100644 (file)
--- a/README
+++ b/README
@@ -2,7 +2,7 @@ dygraphs JavaScript charting library
 Copyright (c) 2006-, Dan Vanderkam.
 http://code.google.com/p/dygraphs/
 
 Copyright (c) 2006-, Dan Vanderkam.
 http://code.google.com/p/dygraphs/
 
-The dygraphs JavaScript library produces produces interactive, zoomable charts of time series based on CSV files.
+The dygraphs JavaScript library produces produces interactive, zoomable charts of time series.
 
 Features
 - Plots time series without using an external server or Flash
 
 Features
 - Plots time series without using an external server or Flash
@@ -12,6 +12,7 @@ Features
 - Interactive zoom
 - Adjustable averaging period
 - Customizable click-through actions
 - Interactive zoom
 - Adjustable averaging period
 - Customizable click-through actions
+- Compatible with the Google Visualization API
 
 Caveats
 - Requires Firefox 1.5+ or Safari/WebKit? 1.3+.
 
 Caveats
 - Requires Firefox 1.5+ or Safari/WebKit? 1.3+.
@@ -26,17 +27,15 @@ Minimal Example
 <script type="text/javascript" src="dygraph-combined.js"></script>
 </head>
 <body>
 <script type="text/javascript" src="dygraph-combined.js"></script>
 </head>
 <body>
-<div id="graphdiv" style="width:400px; height:300px;"></div>
+<div id="graphdiv"></div>
 <script type="text/javascript">
   g = new DateGraph(
         document.getElementById("graphdiv"),  // containing div
 <script type="text/javascript">
   g = new DateGraph(
         document.getElementById("graphdiv"),  // containing div
-        function() {                // function or path to CSV file.
-          return "20080507,75\n" +
-                 "20080508,70\n" +
-                 "20080509,80\n";
-        },
-        [ "Temperature" ],          // names of data series
-        {}                          // additional options (see wiki)
+        "Date,Temperature\n" +                // the data series
+        "2008-05-07,75\n" +
+        "2008-05-08,70\n" +
+        "2008-05-09,80\n";
+        }
       );
 </script>
 </body>
       );
 </script>
 </body>
index 00b86cb..b0aceb3 100644 (file)
@@ -27,7 +27,7 @@
     <a href="http://code.google.com/p/dygraphs/">code.google.com/p/dygraphs</a></p>
   </center>
 
     <a href="http://code.google.com/p/dygraphs/">code.google.com/p/dygraphs</a></p>
   </center>
 
-<p>The dygraphs JavaScript library produces produces interactive, zoomable charts of time series based on CSV files.</p>
+<p>The dygraphs JavaScript library produces produces interactive, zoomable charts of time series.</p>
 
 <h3>Features</h3>
 <ul>
 
 <h3>Features</h3>
 <ul>
@@ -38,6 +38,7 @@
   <li>Interactive zoom</li>
   <li>Adjustable averaging period</li>
   <li>Customizable click-through actions</li>
   <li>Interactive zoom</li>
   <li>Adjustable averaging period</li>
   <li>Customizable click-through actions</li>
+  <li>Compatible with the Google Visualization API</li>
 </ul>
 
 <h3>Caveats</h3>
 </ul>
 
 <h3>Caveats</h3>
@@ -87,7 +88,7 @@
 
 <h2>Usage</h2>
 
 
 <h2>Usage</h2>
 
-<p>The DateGraph library depends on two other JS libraries: <a href="http://www.mochikit.com/">MochiKit</a> and <a href="http://www.liquidx.net/plotkit/">PlotKit</a>. Rather than tracking down copies of these libraries, I recommend using a packed version of dygraphs that combines all three libraries into a single JS file. Either grab this file from dygraph project's <a href="http://code.google.com/p/dygraphs/downloads/list">downloads</a> page or create it yourself by <a href="http://code.google.com/p/dygraphs/source/checkout">checking out</a> a copy of the code and running:
+<p>The dygraphs library depends on two other JS libraries: <a href="http://www.mochikit.com/">MochiKit</a> and <a href="http://www.liquidx.net/plotkit/">PlotKit</a>. Rather than tracking down copies of these libraries, I recommend using a packed version of dygraphs that combines all three libraries into a single JS file. Either grab this file from dygraph project's <a href="http://code.google.com/p/dygraphs/downloads/list">downloads</a> page or create it yourself by <a href="http://code.google.com/p/dygraphs/source/checkout">checking out</a> a copy of the code and running:
 
 <pre>./generate-combined.sh</pre>
 
 
 <pre>./generate-combined.sh</pre>
 
 &lt;script type="text/javascript" src="combined.js"&gt;&lt;/script&gt;
 &lt;/head&gt;
 &lt;body&gt;
 &lt;script type="text/javascript" src="combined.js"&gt;&lt;/script&gt;
 &lt;/head&gt;
 &lt;body&gt;
-&lt;div id="graphdiv" style="width:400px; height:300px;"&gt;&lt;/div&gt;
+&lt;div id="graphdiv"&gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
 &lt;script type="text/javascript"&gt;
-  g = new DateGraph(
+  g = new Dygraph(
         document.getElementById("graphdiv"),  // containing div
         document.getElementById("graphdiv"),  // containing div
-        function() {                // function or path to CSV file.
-          return "20080507,75\n" +
-                 "20080508,70\n" +
-                 "20080509,80\n";
-        },
-        [ "Temperature" ],          // names of data series
-        {}                          // additional options (see below)
+        "Date,Temperature\n" +                // CSV or path to a CSV file.
+        "20080507,75\n" +
+        "20080508,70\n" +
+        "20080509,80\n",
       );
 &lt;/script&gt;
 &lt;/body&gt;
 &lt;/html&gt;
 </pre>
 </td><td valign=top>
       );
 &lt;/script&gt;
 &lt;/body&gt;
 &lt;/html&gt;
 </pre>
 </td><td valign=top>
-  <div id="graphdiv" style="width:400px; height:300px;"></div>
+  <div id="graphdiv"></div>
   <script type="text/javascript">
   <script type="text/javascript">
-    g = new DateGraph(
-          document.getElementById("graphdiv"),
-          function() {                          // function or path to CSV file.
-            return "20080507,75\n" +
-                   "20080508,70\n" +
-                   "20080509,80\n";
-          },
-          [ "Temperature" ],                    // names of data series
-          {}                                    // additional options
-        );
+    g = new Dygraph(
+        document.getElementById("graphdiv"),  // containing div
+        "Date,Temperature\n" +                // CSV or path to a CSV file.
+        "20080507,75\n" +
+        "20080508,70\n" +
+        "20080509,80\n"
+      );
   </script>
 </td></tr></table>
 
   </script>
 </td></tr></table>
 
-<p>In order to keep this example self-contained, the second parameter is a function that returns CSV data. These lines <i>must</i> begin with a date in the form <i>YYYYMMDD</i>. In most applications, it makes more sense to include a CSV file instead. If the second parameter to the constructor is a string, it will be interpreted as the path to a CSV file. The DateGraph will perform an XMLHttpRequest to retrieve this file and display the data when it becomes available. Make sure your CSV file is readable and serving from a place that understands XMLHttpRequest's! In particular, you cannot specify a CSV file using <code>"file:///"</code>. Here's an example: (data from <a href="http://www.wunderground.com/history/airport/KNUQ/2007/1/1/CustomHistory.html?dayend=31&monthend=12&yearend=2007&req_city=NA&req_state=NA&req_statename=NA">Weather Underground</a>)</p>
+<p>In order to keep this example self-contained, the second parameter is a function that returns CSV data. These lines <i>must</i> begin with a date in the form <i>YYYYMMDD</i>. In most applications, it makes more sense to include a CSV file instead. If the second parameter to the constructor is a string, it will be interpreted as the path to a CSV file. The Dygraph will perform an XMLHttpRequest to retrieve this file and display the data when it becomes available. Make sure your CSV file is readable and serving from a place that understands XMLHttpRequest's! In particular, you cannot specify a CSV file using <code>"file:///"</code>. Here's an example: (data from <a href="http://www.wunderground.com/history/airport/KNUQ/2007/1/1/CustomHistory.html?dayend=31&monthend=12&yearend=2007&req_city=NA&req_state=NA&req_statename=NA">Weather Underground</a>)</p>
 
 <table>
   <tr><th>HTML</th>
 
 <table>
   <tr><th>HTML</th>
 &lt;body&gt;
 &lt;div id="graphdiv" style="width:600px; height:300px;"&gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
 &lt;body&gt;
 &lt;div id="graphdiv" style="width:600px; height:300px;"&gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
-  g = new DateGraph(
+  g = new Dygraph(
         document.getElementById("graphdiv"),
         "temperatures.csv",  // path to CSV file
         document.getElementById("graphdiv"),
         "temperatures.csv",  // path to CSV file
-        null,                // labels in top line of CSV file
-        {}
+        {}                   // additional options
       );
 &lt;/script&gt;
 &lt;/body&gt;
       );
 &lt;/script&gt;
 &lt;/body&gt;
 </td><td valign=top>
   <div id="graphdiv2" style="width:600px; height:300px;"></div>
   <script type="text/javascript">
 </td><td valign=top>
   <div id="graphdiv2" style="width:600px; height:300px;"></div>
   <script type="text/javascript">
-    g2 = new DateGraph(
+    g2 = new Dygraph(
           document.getElementById("graphdiv2"),
           document.getElementById("graphdiv2"),
-          "temperatures.csv", null, {}
+          "temperatures.csv", {}
         );
   </script>
 </td></tr></table>
         );
   </script>
 </td></tr></table>
 <p>Click <a href="temperatures.csv">here</a> to view the <code>temperatures.csv</code> file. There are a few things to note here:</p>
 
 <ul>
 <p>Click <a href="temperatures.csv">here</a> to view the <code>temperatures.csv</code> file. There are a few things to note here:</p>
 
 <ul>
-  <li>Because the third parameter to the DateGraph constructor was <code>null</code>, the labels were taken from the first line of the data instead. The first line of <code>temperatures.csv</code> is <code>Date,High,Low</code>.</li>
-  <li>DateGraph automatically chose two different, easily-distinguishable colors for the two data series.</li>
+  <li>The Dygraph sent off an XHR to get the temperatures.csv file.</li>
+  <li>The labels were taken from the first line of <code>temperatures.csv</code>, which is <code>Date,High,Low</code>.</li>
+  <li>The Dygraph automatically chose two different, easily-distinguishable colors for the two data series.</li>
   <li>The labels on the x-axis have switched from days to months. If you zoom in, they'll switch to weeks and then days.</li>
   <li>The labels on the x-axis have switched from days to months. If you zoom in, they'll switch to weeks and then days.</li>
-  <li>Some heuristics are used to determine a good vertical range for the data. The idea is to make all the data visible and have human-friendly values on the axis (i.e. 200 instead of 193.4). Generally this works well, but in this case the vertical range is way too large.</li>
+  <li>Some heuristics are used to determine a good vertical range for the data. The idea is to make all the data visible and have human-friendly values on the axis (i.e. 200 instead of 193.4). Generally this works well.</li>
   <li>The data is very spiky. A moving average would be easier to interpret.</li>
 </ul>
 
   <li>The data is very spiky. A moving average would be easier to interpret.</li>
 </ul>
 
-<p>These last two problems can be fixed by specifying the appropriate options in the fourth parameter to the DateGraph constructor. To set the number of days for a moving average, use the <b>rollPeriod</b> option. To set the range of the y-axis, use the <b>valueRange</b> option. Here's how it's done:</p>
+<p>This problem can be fixed by specifying the appropriate options in the "additional options" parameter to the Dygraph constructor. To set the number of days for a moving average, use the <b>rollPeriod</b> option. Here's how it's done:</p>
 
 <table>
   <tr><th>HTML</th>
 
 <table>
   <tr><th>HTML</th>
 &lt;body&gt;
 &lt;div id="graphdiv" style="width:600px; height:300px;"&gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
 &lt;body&gt;
 &lt;div id="graphdiv" style="width:600px; height:300px;"&gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
-  g = new DateGraph(
+  g = new Dygraph(
         document.getElementById("graphdiv"),
         document.getElementById("graphdiv"),
-        "temperatures.csv", null,
+        "temperatures.csv",
         { rollPeriod: 7,
           showRoller: true,
         { rollPeriod: 7,
           showRoller: true,
-          valueRange: [25, 100]
         }
       );
 &lt;/script&gt;
         }
       );
 &lt;/script&gt;
 </td><td valign=top>
   <div id="graphdiv3" style="width:600px; height:300px;"></div>
   <script type="text/javascript">
 </td><td valign=top>
   <div id="graphdiv3" style="width:600px; height:300px;"></div>
   <script type="text/javascript">
-    g3 = new DateGraph(
+    g3 = new Dygraph(
           document.getElementById("graphdiv3"),
           document.getElementById("graphdiv3"),
-          "temperatures.csv", null,
+          "temperatures.csv",
           { rollPeriod: 7,
             showRoller: true,
           { rollPeriod: 7,
             showRoller: true,
-            valueRange: [25, 100]
           }
         );
   </script>
           }
         );
   </script>
 <p>A rolling average can be set using the text box in the lower left-hand corner of the graph (the showRoller attribute is what makes this appear).</p>
 
 <h2>Error Bars</h2>
 <p>A rolling average can be set using the text box in the lower left-hand corner of the graph (the showRoller attribute is what makes this appear).</p>
 
 <h2>Error Bars</h2>
-<p>Another significant feature of the dygraphs library is the ability to display error bars around data series. One standard deviation must be specified for each data point. A +/-<i>n</i> sigma band will be drawn around the data series at that point. If a moving average is being displayed, DateGraph will compute the standard deviation of the average at each point. (i.e. <i>&sigma;</i> = sqrt((<i>&sigma;_1</i>^2 + <i>&sigma;_2</i>^2 + ... + <i>&sigma;_n</i>^2)/<i>n</i>))</p>
+<p>Another significant feature of the dygraphs library is the ability to display error bars around data series. One standard deviation must be specified for each data point. A +/-<i>n</i> sigma band will be drawn around the data series at that point. If a moving average is being displayed, dygraphs will compute the standard deviation of the average at each point. (i.e. <i>&sigma;</i> = sqrt((<i>&sigma;_1</i>^2 + <i>&sigma;_2</i>^2 + ... + <i>&sigma;_n</i>^2)/<i>n</i>))</p>
 
 <p>Here's a demonstration. There are two data series. One is <code>N(100,10)</code> with a standard deviation of 10 specified at each point. The other is <code>N(80,20)</code> with a standard deviation of 20 specified at each point. The CSV file was generated using Octave and can be viewed <a href="twonormals.csv">here</a>.</p>
 
 
 <p>Here's a demonstration. There are two data series. One is <code>N(100,10)</code> with a standard deviation of 10 specified at each point. The other is <code>N(80,20)</code> with a standard deviation of 20 specified at each point. The CSV file was generated using Octave and can be viewed <a href="twonormals.csv">here</a>.</p>
 
  &gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
 $ = document.getElementById;
  &gt;&lt;/div&gt;
 &lt;script type="text/javascript"&gt;
 $ = document.getElementById;
-g = new DateGraph(
+g = new Dygraph(
   $("graphdiv"),
   "twonormals.csv",
   $("graphdiv"),
   "twonormals.csv",
-  null,
   { rollPeriod: 7,
     showRoller: true,
     errorBars: true,
   { rollPeriod: 7,
     showRoller: true,
     errorBars: true,
@@ -264,10 +256,9 @@ g = new DateGraph(
   <div id="graphdiv4" style="width:800px; height:400px;"></div>
   <script type="text/javascript">
 $ = document.getElementById;
   <div id="graphdiv4" style="width:800px; height:400px;"></div>
   <script type="text/javascript">
 $ = document.getElementById;
-new DateGraph(
+new Dygraph(
   document.getElementById("graphdiv4"),
   "twonormals.csv",
   document.getElementById("graphdiv4"),
   "twonormals.csv",
-  null,
   { rollPeriod: 14,
     showRoller: true,
     errorBars: true,
   { rollPeriod: 14,
     showRoller: true,
     errorBars: true,
@@ -292,10 +283,9 @@ new DateGraph(
 <div id=dow_chart style="width:1000px; height:400px;"></div>
 <script type="text/javascript">
   // From http://www.econstats.com/eqty/eq_d_mi_3.csv
 <div id=dow_chart style="width:1000px; height:400px;"></div>
 <script type="text/javascript">
   // From http://www.econstats.com/eqty/eq_d_mi_3.csv
-  dow = new DateGraph(
+  dow = new Dygraph(
     document.getElementById('dow_chart'),
     "dow.txt",
     document.getElementById('dow_chart'),
     "dow.txt",
-    null,
     {
       showRoller: true,
       customBars: true,
     {
       showRoller: true,
       customBars: true,
@@ -306,7 +296,7 @@ new DateGraph(
 
 
 <h2>Other Options</h2>
 
 
 <h2>Other Options</h2>
-<p>These are the options that can be passed in through the fourth parameter of the DateGraph constructor.</p>
+<p>These are the options that can be passed in through the optional third parameter of the Dygraph constructor.</p>
 
 <table class=thinborder width=1000>
   <tr><th>Name</th><th>Sample Value</th><th>Description</th></tr>
 
 <table class=thinborder width=1000>
   <tr><th>Name</th><th>Sample Value</th><th>Description</th></tr>
@@ -395,7 +385,7 @@ new DateGraph(
   </tr>
 </table>
 
   </tr>
 </table>
 
-<p>Any options you specify also get passed on to PlotKit's <a href="http://media.liquidx.net/js/plotkit-doc/PlotKit.Renderer.html">Renderer</a> class. DateGraph will override some of these (e.g. strokeColor), but others may be useful. The <code>padding</code> property is an example of this.</p>
+<p>Any options you specify also get passed on to PlotKit's <a href="http://media.liquidx.net/js/plotkit-doc/PlotKit.Renderer.html">Renderer</a> class. dygraphs will override some of these (e.g. strokeColor), but others may be useful. The <code>padding</code> property is an example of this.</p>
 
 <h2>Common Gotchas</h2>
 <p>Here are a few problems that I've frequently run into while using the
 
 <h2>Common Gotchas</h2>
 <p>Here are a few problems that I've frequently run into while using the
@@ -408,22 +398,15 @@ dygraphs library.</p>
     href="http://www.getfirebug.com/">Firebug</a>.</li>
 
   <li>Make sure your CSV files are in the correct format. They must be of the
     href="http://www.getfirebug.com/">Firebug</a>.</li>
 
   <li>Make sure your CSV files are in the correct format. They must be of the
-  form <code>YYYYMMDD,series1,series2,...</code>. If you're specifying the
-  names of each data series in the CSV file itself, make sure that you pass
-  <code>null</code> as the third parameter to the DateGraph constructor to let
-  the library know that. And if you set the <code>errorBars</code> property,
-  make sure you alternate data series and standard deviations.</li>
+  form <code>YYYYMMDD,series1,series2,...</code>. And if you set the
+  <code>errorBars</code> property, make sure you alternate data series and
+  standard deviations.</li>
 
   <li>dygraphs are not happy when placed inside a <code>&lt;center&gt;</code>
   tag. This applies to the CSS <code>text-align</code> property as well. If you
 
   <li>dygraphs are not happy when placed inside a <code>&lt;center&gt;</code>
   tag. This applies to the CSS <code>text-align</code> property as well. If you
-  want to center a DateGraph, put it inside a table with "align=center"
+  want to center a Dygraph, put it inside a table with "align=center"
   set.</li>
 
   set.</li>
 
-  <li>If you specify the <code>colors</code> property or name the data series
-  using the third parameter of the DateGraph constructor, make sure the number
-  of data series agree in all places: <code>colors</code>, third parameter and
-  in each line of the CSV file itself.</li>
-
   <li>Don't set the <code>dateWindow</code> property to a date. It expects
   milliseconds since epoch, which can be obtained from a JavaScript Date
   object's valueOf method.</li>
   <li>Don't set the <code>dateWindow</code> property to a date. It expects
   milliseconds since epoch, which can be obtained from a JavaScript Date
   object's valueOf method.</li>
index 218d41e..e03c840 100644 (file)
@@ -3,7 +3,7 @@
 
 /**
  * @fileoverview Subclasses various parts of PlotKit to meet the additional
 
 /**
  * @fileoverview Subclasses various parts of PlotKit to meet the additional
- * needs of DateGraph: grid overlays and error bars
+ * needs of Dygraph: grid overlays and error bars
  */
 
 // Subclass PlotKit.Layout to add:
  */
 
 // Subclass PlotKit.Layout to add:
 // 2. Copy error terms for PlotKit.CanvasRenderer._renderLineChart
 
 /**
 // 2. Copy error terms for PlotKit.CanvasRenderer._renderLineChart
 
 /**
- * Creates a new DateGraphLayout object. Options are the same as those allowed
+ * Creates a new DygraphLayout object. Options are the same as those allowed
  * by the PlotKit.Layout constructor.
  * @param {Object} options Options for PlotKit.Layout
  * by the PlotKit.Layout constructor.
  * @param {Object} options Options for PlotKit.Layout
- * @return {Object} The DateGraphLayout object
+ * @return {Object} The DygraphLayout object
  */
  */
-DateGraphLayout = function(options) {
+DygraphLayout = function(options) {
   PlotKit.Layout.call(this, "line", options);
 };
   PlotKit.Layout.call(this, "line", options);
 };
-DateGraphLayout.prototype = new PlotKit.Layout();
+DygraphLayout.prototype = new PlotKit.Layout();
 
 /**
  * Behaves the same way as PlotKit.Layout, but also copies the errors
  * @private
  */
 
 /**
  * Behaves the same way as PlotKit.Layout, but also copies the errors
  * @private
  */
-DateGraphLayout.prototype.evaluateWithError = function() {
+DygraphLayout.prototype.evaluateWithError = function() {
   this.evaluate();
   if (!this.options.errorBars) return;
 
   this.evaluate();
   if (!this.options.errorBars) return;
 
@@ -52,7 +52,7 @@ DateGraphLayout.prototype.evaluateWithError = function() {
 /**
  * Convenience function to remove all the data sets from a graph
  */
 /**
  * Convenience function to remove all the data sets from a graph
  */
-DateGraphLayout.prototype.removeAllDatasets = function() {
+DygraphLayout.prototype.removeAllDatasets = function() {
   delete this.datasets;
   this.datasets = new Array();
 };
   delete this.datasets;
   this.datasets = new Array();
 };
@@ -61,7 +61,7 @@ DateGraphLayout.prototype.removeAllDatasets = function() {
  * Change the values of various layout options
  * @param {Object} new_options an associative array of new properties
  */
  * Change the values of various layout options
  * @param {Object} new_options an associative array of new properties
  */
-DateGraphLayout.prototype.updateOptions = function(new_options) {
+DygraphLayout.prototype.updateOptions = function(new_options) {
   MochiKit.Base.update(this.options, new_options ? new_options : {});
 };
 
   MochiKit.Base.update(this.options, new_options ? new_options : {});
 };
 
@@ -72,10 +72,10 @@ DateGraphLayout.prototype.updateOptions = function(new_options) {
 /**
  * Sets some PlotKit.CanvasRenderer options
  * @param {Object} element The canvas to attach to
 /**
  * Sets some PlotKit.CanvasRenderer options
  * @param {Object} element The canvas to attach to
- * @param {Layout} layout The DateGraphLayout object for this graph.
+ * @param {Layout} layout The DygraphLayout object for this graph.
  * @param {Object} options Options to pass on to CanvasRenderer
  */
  * @param {Object} options Options to pass on to CanvasRenderer
  */
-DateGraphCanvasRenderer = function(element, layout, options) {
+DygraphCanvasRenderer = function(element, layout, options) {
   PlotKit.CanvasRenderer.call(this, element, layout, options);
   this.options.shouldFill = false;
   this.options.shouldStroke = true;
   PlotKit.CanvasRenderer.call(this, element, layout, options);
   this.options.shouldFill = false;
   this.options.shouldStroke = true;
@@ -87,12 +87,12 @@ DateGraphCanvasRenderer = function(element, layout, options) {
   // TODO(danvk) This shouldn't be necessary: effects should be overlaid
   this.options.drawBackground = false;
 };
   // TODO(danvk) This shouldn't be necessary: effects should be overlaid
   this.options.drawBackground = false;
 };
-DateGraphCanvasRenderer.prototype = new PlotKit.CanvasRenderer();
+DygraphCanvasRenderer.prototype = new PlotKit.CanvasRenderer();
 
 /**
  * Draw an X/Y grid on top of the existing plot
  */
 
 /**
  * Draw an X/Y grid on top of the existing plot
  */
-DateGraphCanvasRenderer.prototype.render = function() {
+DygraphCanvasRenderer.prototype.render = function() {
   // Draw the new X/Y grid
   var ctx = this.element.getContext("2d");
   if (this.options.drawYGrid) {
   // Draw the new X/Y grid
   var ctx = this.element.getContext("2d");
   if (this.options.drawYGrid) {
@@ -136,7 +136,7 @@ DateGraphCanvasRenderer.prototype.render = function() {
 /**
  * Overrides the CanvasRenderer method to draw error bars
  */
 /**
  * Overrides the CanvasRenderer method to draw error bars
  */
-DateGraphCanvasRenderer.prototype._renderLineChart = function() {
+DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var context = this.element.getContext("2d");
   var colorCount = this.options.colorScheme.length;
   var colorScheme = this.options.colorScheme;
   var context = this.element.getContext("2d");
   var colorCount = this.options.colorScheme.length;
   var colorScheme = this.options.colorScheme;
index cd7fdb7..38705bf 100644 (file)
@@ -1593,7 +1593,7 @@ throw MochiKit.Iter.StopIteration;
 return rval;
 }};
 }});
 return rval;
 }};
 }});
-MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter",];
+MochiKit.Iter.EXPORT_OK=["iteratorRegistry","arrayLikeIter","hasIterateNext","iterateNextIter"];
 MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"];
 MochiKit.Iter.__new__=function(){
 var m=MochiKit.Base;
 MochiKit.Iter.EXPORT=["StopIteration","registerIteratorFactory","iter","count","cycle","repeat","next","izip","ifilter","ifilterfalse","islice","imap","applymap","chain","takewhile","dropwhile","tee","list","reduce","range","sum","exhaust","forEach","every","sorted","reversed","some","iextend","groupby","groupby_as_array"];
 MochiKit.Iter.__new__=function(){
 var m=MochiKit.Base;
@@ -4603,11 +4603,11 @@ MochiKit.Base._exportSymbols(this,PlotKit.Canvas);
 
 
 Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(x,pad,r){if(typeof (r)=="undefined"){r=10}for(;parseInt(x,10)<r&&r>1;r/=10){x=pad.toString()+x}return x.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(d){return Date.ext.locales[d.locale].a[d.getDay()]},A:function(d){return Date.ext.locales[d.locale].A[d.getDay()]},b:function(d){return Date.ext.locales[d.locale].b[d.getMonth()]},B:function(d){return Date.ext.locales[d.locale].B[d.getMonth()]},c:"toLocaleString",C:function(d){return Date.ext.util.xPad(parseInt(d.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(d){return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100,10),0)},G:function(d){var y=d.getFullYear();var V=parseInt(Date.ext.formats.V(d),10);var W=parseInt(Date.ext.formats.W(d),10);if(W>V){y++}else{if(W===0&&V>=52){y--}}return y},H:["getHours","0"],I:function(d){var I=d.getHours()%12;return Date.ext.util.xPad(I===0?12:I,0)},j:function(d){var ms=d-new Date(""+d.getFullYear()+"/1/1 GMT");ms+=d.getTimezoneOffset()*60000;var doy=parseInt(ms/60000/60/24,10)+1;return Date.ext.util.xPad(doy,0,100)},m:function(d){return Date.ext.util.xPad(d.getMonth()+1,0)},M:["getMinutes","0"],p:function(d){return Date.ext.locales[d.locale].p[d.getHours()>=12?1:0]},P:function(d){return Date.ext.locales[d.locale].P[d.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(d){var dow=d.getDay();return dow===0?7:dow},U:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=6-d.getDay();var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0)},V:function(d){var woy=parseInt(Date.ext.formats.W(d),10);var dow1_1=(new Date(""+d.getFullYear()+"/1/1")).getDay();var idow=woy+(dow1_1>4||dow1_1<=1?0:1);if(idow==53&&(new Date(""+d.getFullYear()+"/12/31")).getDay()<4){idow=1}else{if(idow===0){idow=Date.ext.formats.V(new Date(""+(d.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(idow,0)},w:"getDay",W:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=7-Date.ext.formats.u(d);var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0,10)},y:function(d){return Date.ext.util.xPad(d.getFullYear()%100,0)},Y:"getFullYear",z:function(d){var o=d.getTimezoneOffset();var H=Date.ext.util.xPad(parseInt(Math.abs(o/60),10),0);var M=Date.ext.util.xPad(o%60,0);return(o>0?"-":"+")+H+M},Z:function(d){return d.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(d){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(fmt){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var d=this;while(fmt.match(/%[cDhnrRtTxXzZ]/)){fmt=fmt.replace(/%([cDhnrRtTxXzZ])/g,function(m0,m1){var f=Date.ext.aggregates[m1];return(f=="locale"?Date.ext.locales[d.locale][m1]:f)})}var str=fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(m0,m1){var f=Date.ext.formats[m1];if(typeof (f)=="string"){return d[f]()}else{if(typeof (f)=="function"){return f.call(d,d)}else{if(typeof (f)=="object"&&typeof (f[0])=="string"){return Date.ext.util.xPad(d[f[0]](),f[1])}else{return m1}}}});d=null;return str};
 
 
 Date.ext={};Date.ext.util={};Date.ext.util.xPad=function(x,pad,r){if(typeof (r)=="undefined"){r=10}for(;parseInt(x,10)<r&&r>1;r/=10){x=pad.toString()+x}return x.toString()};Date.prototype.locale="en-GB";if(document.getElementsByTagName("html")&&document.getElementsByTagName("html")[0].lang){Date.prototype.locale=document.getElementsByTagName("html")[0].lang}Date.ext.locales={};Date.ext.locales.en={a:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],A:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],b:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],B:["January","February","March","April","May","June","July","August","September","October","November","December"],c:"%a %d %b %Y %T %Z",p:["AM","PM"],P:["am","pm"],x:"%d/%m/%y",X:"%T"};Date.ext.locales["en-US"]=Date.ext.locales.en;Date.ext.locales["en-US"].c="%a %d %b %Y %r %Z";Date.ext.locales["en-US"].x="%D";Date.ext.locales["en-US"].X="%r";Date.ext.locales["en-GB"]=Date.ext.locales.en;Date.ext.locales["en-AU"]=Date.ext.locales["en-GB"];Date.ext.formats={a:function(d){return Date.ext.locales[d.locale].a[d.getDay()]},A:function(d){return Date.ext.locales[d.locale].A[d.getDay()]},b:function(d){return Date.ext.locales[d.locale].b[d.getMonth()]},B:function(d){return Date.ext.locales[d.locale].B[d.getMonth()]},c:"toLocaleString",C:function(d){return Date.ext.util.xPad(parseInt(d.getFullYear()/100,10),0)},d:["getDate","0"],e:["getDate"," "],g:function(d){return Date.ext.util.xPad(parseInt(Date.ext.util.G(d)/100,10),0)},G:function(d){var y=d.getFullYear();var V=parseInt(Date.ext.formats.V(d),10);var W=parseInt(Date.ext.formats.W(d),10);if(W>V){y++}else{if(W===0&&V>=52){y--}}return y},H:["getHours","0"],I:function(d){var I=d.getHours()%12;return Date.ext.util.xPad(I===0?12:I,0)},j:function(d){var ms=d-new Date(""+d.getFullYear()+"/1/1 GMT");ms+=d.getTimezoneOffset()*60000;var doy=parseInt(ms/60000/60/24,10)+1;return Date.ext.util.xPad(doy,0,100)},m:function(d){return Date.ext.util.xPad(d.getMonth()+1,0)},M:["getMinutes","0"],p:function(d){return Date.ext.locales[d.locale].p[d.getHours()>=12?1:0]},P:function(d){return Date.ext.locales[d.locale].P[d.getHours()>=12?1:0]},S:["getSeconds","0"],u:function(d){var dow=d.getDay();return dow===0?7:dow},U:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=6-d.getDay();var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0)},V:function(d){var woy=parseInt(Date.ext.formats.W(d),10);var dow1_1=(new Date(""+d.getFullYear()+"/1/1")).getDay();var idow=woy+(dow1_1>4||dow1_1<=1?0:1);if(idow==53&&(new Date(""+d.getFullYear()+"/12/31")).getDay()<4){idow=1}else{if(idow===0){idow=Date.ext.formats.V(new Date(""+(d.getFullYear()-1)+"/12/31"))}}return Date.ext.util.xPad(idow,0)},w:"getDay",W:function(d){var doy=parseInt(Date.ext.formats.j(d),10);var rdow=7-Date.ext.formats.u(d);var woy=parseInt((doy+rdow)/7,10);return Date.ext.util.xPad(woy,0,10)},y:function(d){return Date.ext.util.xPad(d.getFullYear()%100,0)},Y:"getFullYear",z:function(d){var o=d.getTimezoneOffset();var H=Date.ext.util.xPad(parseInt(Math.abs(o/60),10),0);var M=Date.ext.util.xPad(o%60,0);return(o>0?"-":"+")+H+M},Z:function(d){return d.toString().replace(/^.*\(([^)]+)\)$/,"$1")},"%":function(d){return"%"}};Date.ext.aggregates={c:"locale",D:"%m/%d/%y",h:"%b",n:"\n",r:"%I:%M:%S %p",R:"%H:%M",t:"\t",T:"%H:%M:%S",x:"locale",X:"locale"};Date.ext.aggregates.z=Date.ext.formats.z(new Date());Date.ext.aggregates.Z=Date.ext.formats.Z(new Date());Date.ext.unsupported={};Date.prototype.strftime=function(fmt){if(!(this.locale in Date.ext.locales)){if(this.locale.replace(/-[a-zA-Z]+$/,"") in Date.ext.locales){this.locale=this.locale.replace(/-[a-zA-Z]+$/,"")}else{this.locale="en-GB"}}var d=this;while(fmt.match(/%[cDhnrRtTxXzZ]/)){fmt=fmt.replace(/%([cDhnrRtTxXzZ])/g,function(m0,m1){var f=Date.ext.aggregates[m1];return(f=="locale"?Date.ext.locales[d.locale][m1]:f)})}var str=fmt.replace(/%([aAbBCdegGHIjmMpPSuUVwWyY%])/g,function(m0,m1){var f=Date.ext.formats[m1];if(typeof (f)=="string"){return d[f]()}else{if(typeof (f)=="function"){return f.call(d,d)}else{if(typeof (f)=="object"&&typeof (f[0])=="string"){return Date.ext.util.xPad(d[f[0]](),f[1])}else{return m1}}}});d=null;return str};
-DateGraphLayout=function(_1){
+DygraphLayout=function(_1){
 PlotKit.Layout.call(this,"line",_1);
 };
 PlotKit.Layout.call(this,"line",_1);
 };
-DateGraphLayout.prototype=new PlotKit.Layout();
-DateGraphLayout.prototype.evaluateWithError=function(){
+DygraphLayout.prototype=new PlotKit.Layout();
+DygraphLayout.prototype.evaluateWithError=function(){
 this.evaluate();
 if(!this.options.errorBars){
 return;
 this.evaluate();
 if(!this.options.errorBars){
 return;
@@ -4630,14 +4630,14 @@ this.points[i].errorPlus=parseFloat(_6[3]);
 }
 }
 };
 }
 }
 };
-DateGraphLayout.prototype.removeAllDatasets=function(){
+DygraphLayout.prototype.removeAllDatasets=function(){
 delete this.datasets;
 this.datasets=new Array();
 };
 delete this.datasets;
 this.datasets=new Array();
 };
-DateGraphLayout.prototype.updateOptions=function(_9){
+DygraphLayout.prototype.updateOptions=function(_9){
 MochiKit.Base.update(this.options,_9?_9:{});
 };
 MochiKit.Base.update(this.options,_9?_9:{});
 };
-DateGraphCanvasRenderer=function(_10,_11,_12){
+DygraphCanvasRenderer=function(_10,_11,_12){
 PlotKit.CanvasRenderer.call(this,_10,_11,_12);
 this.options.shouldFill=false;
 this.options.shouldStroke=true;
 PlotKit.CanvasRenderer.call(this,_10,_11,_12);
 this.options.shouldFill=false;
 this.options.shouldStroke=true;
@@ -4647,8 +4647,8 @@ this.options.gridLineColor=MochiKit.Color.Color.grayColor();
 MochiKit.Base.update(this.options,_12);
 this.options.drawBackground=false;
 };
 MochiKit.Base.update(this.options,_12);
 this.options.drawBackground=false;
 };
-DateGraphCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
-DateGraphCanvasRenderer.prototype.render=function(){
+DygraphCanvasRenderer.prototype=new PlotKit.CanvasRenderer();
+DygraphCanvasRenderer.prototype.render=function(){
 var ctx=this.element.getContext("2d");
 if(this.options.drawYGrid){
 var _14=this.layout.yticks;
 var ctx=this.element.getContext("2d");
 if(this.options.drawYGrid){
 var _14=this.layout.yticks;
@@ -4683,7 +4683,7 @@ ctx.stroke();
 this._renderLineChart();
 this._renderLineAxis();
 };
 this._renderLineChart();
 this._renderLineAxis();
 };
-DateGraphCanvasRenderer.prototype._renderLineChart=function(){
+DygraphCanvasRenderer.prototype._renderLineChart=function(){
 var _17=this.element.getContext("2d");
 var _18=this.options.colorScheme.length;
 var _19=this.options.colorScheme;
 var _17=this.element.getContext("2d");
 var _18=this.options.colorScheme.length;
 var _19=this.options.colorScheme;
@@ -4765,81 +4765,130 @@ _23(_35,this)(_17);
 _23(_27,this)(_17);
 _17.restore();
 };
 _23(_27,this)(_17);
 _17.restore();
 };
-DateGraph=function(div,_49,_50,_51){
+Dygraph=function(div,_49,_50){
 if(arguments.length>0){
 if(arguments.length>0){
-this.__init__(div,_49,_50,_51);
+if(arguments.length==4){
+this.warn("Using deprecated four-argument dygraph constructor");
+this.__old_init__(div,_49,arguments[2],arguments[3]);
+}else{
+this.__init__(div,_49,_50);
+}
 }
 };
 }
 };
-DateGraph.NAME="DateGraph";
-DateGraph.VERSION="1.1";
-DateGraph.__repr__=function(){
+Dygraph.NAME="Dygraph";
+Dygraph.VERSION="1.2";
+Dygraph.__repr__=function(){
 return "["+this.NAME+" "+this.VERSION+"]";
 };
 return "["+this.NAME+" "+this.VERSION+"]";
 };
-DateGraph.toString=function(){
+Dygraph.toString=function(){
 return this.__repr__();
 };
 return this.__repr__();
 };
-DateGraph.DEFAULT_ROLL_PERIOD=1;
-DateGraph.DEFAULT_WIDTH=480;
-DateGraph.DEFAULT_HEIGHT=320;
-DateGraph.DEFAULT_STROKE_WIDTH=1;
-DateGraph.AXIS_LINE_WIDTH=0.3;
-DateGraph.DEFAULT_ATTRS={highlightCircleSize:3,pixelsPerXLabel:60,pixelsPerYLabel:30,labelsDivWidth:250,labelsDivStyles:{}};
-DateGraph.prototype.__init__=function(div,_52,_53,_54){
+Dygraph.DEFAULT_ROLL_PERIOD=1;
+Dygraph.DEFAULT_WIDTH=480;
+Dygraph.DEFAULT_HEIGHT=320;
+Dygraph.AXIS_LINE_WIDTH=0.3;
+Dygraph.DEFAULT_ATTRS={highlightCircleSize:3,pixelsPerXLabel:60,pixelsPerYLabel:30,labelsDivWidth:250,labelsDivStyles:{},labelsSeparateLines:false,labelsKMB:false,strokeWidth:1,showRoller:false,xValueFormatter:Dygraph.dateString_,xValueParser:Dygraph.dateParser,xTicker:Dygraph.dateTicker,sigma:2,errorBars:false,fractions:false,wilsonInterval:true,customBars:false};
+Dygraph.DEBUG=1;
+Dygraph.INFO=2;
+Dygraph.WARNING=3;
+Dygraph.ERROR=3;
+Dygraph.prototype.__old_init__=function(div,_51,_52,_53){
+if(_52!=null){
+var _54=["Date"];
+for(var i=0;i<_52.length;i++){
+_54.push(_52[i]);
+}
+MochiKit.Base.update(_53,{"labels":_54});
+}
+this.__init__(div,_51,_53);
+};
+Dygraph.prototype.__init__=function(div,_55,_56){
+if(_56==null){
+_56={};
+}
 this.maindiv_=div;
 this.maindiv_=div;
-this.labels_=_53;
-this.file_=_52;
-this.rollPeriod_=_54.rollPeriod||DateGraph.DEFAULT_ROLL_PERIOD;
+this.file_=_55;
+this.rollPeriod_=_56.rollPeriod||Dygraph.DEFAULT_ROLL_PERIOD;
 this.previousVerticalX_=-1;
 this.previousVerticalX_=-1;
+this.fractions_=_56.fractions||false;
+this.dateWindow_=_56.dateWindow||null;
+this.valueRange_=_56.valueRange||null;
+this.wilsonInterval_=_56.wilsonInterval||true;
+this.customBars_=_56.customBars||false;
+if(div.style.width==""){
+div.style.width=Dygraph.DEFAULT_WIDTH+"px";
+}
+if(div.style.height==""){
+div.style.height=Dygraph.DEFAULT_HEIGHT+"px";
+}
 this.width_=parseInt(div.style.width,10);
 this.height_=parseInt(div.style.height,10);
 this.width_=parseInt(div.style.width,10);
 this.height_=parseInt(div.style.height,10);
-this.errorBars_=_54.errorBars||false;
-this.fractions_=_54.fractions||false;
-this.strokeWidth_=_54.strokeWidth||DateGraph.DEFAULT_STROKE_WIDTH;
-this.dateWindow_=_54.dateWindow||null;
-this.valueRange_=_54.valueRange||null;
-this.labelsSeparateLines=_54.labelsSeparateLines||false;
-this.labelsDiv_=_54.labelsDiv||null;
-this.labelsKMB_=_54.labelsKMB||false;
-this.xValueParser_=_54.xValueParser||DateGraph.prototype.dateParser;
-this.xValueFormatter_=_54.xValueFormatter||DateGraph.prototype.dateString_;
-this.xTicker_=_54.xTicker||DateGraph.prototype.dateTicker;
-this.sigma_=_54.sigma||2;
-this.wilsonInterval_=_54.wilsonInterval||true;
-this.customBars_=_54.customBars||false;
+this.user_attrs_={};
+MochiKit.Base.update(this.user_attrs_,_56);
 this.attrs_={};
 this.attrs_={};
-MochiKit.Base.update(this.attrs_,DateGraph.DEFAULT_ATTRS);
-MochiKit.Base.update(this.attrs_,_54);
-if(typeof this.attrs_.pixelsPerXLabel=="undefined"){
-this.attrs_.pixelsPerXLabel=60;
-}
-this.labelsFromCSV_=(this.labels_==null);
-if(this.labels_==null){
-this.labels_=[];
-}
-this.clickCallback_=_54.clickCallback||null;
-this.zoomCallback_=_54.zoomCallback||null;
+MochiKit.Base.update(this.attrs_,Dygraph.DEFAULT_ATTRS);
+this.labelsFromCSV_=(this.attr_("labels")==null);
 this.createInterface_();
 this.createInterface_();
-this.layoutOptions_={"errorBars":(this.errorBars_||this.customBars_),"xOriginIsZero":false};
-MochiKit.Base.update(this.layoutOptions_,_54);
-this.setColors_(_54);
-this.layout_=new DateGraphLayout(this.layoutOptions_);
-this.renderOptions_={colorScheme:this.colors_,strokeColor:null,strokeWidth:this.strokeWidth_,axisLabelFontSize:14,axisLineWidth:DateGraph.AXIS_LINE_WIDTH};
-MochiKit.Base.update(this.renderOptions_,_54);
-this.plotter_=new DateGraphCanvasRenderer(this.hidden_,this.layout_,this.renderOptions_);
+this.layoutOptions_={"errorBars":(this.attr_("errorBars")||this.customBars_),"xOriginIsZero":false};
+MochiKit.Base.update(this.layoutOptions_,this.attrs_);
+MochiKit.Base.update(this.layoutOptions_,this.user_attrs_);
+this.layout_=new DygraphLayout(this.layoutOptions_);
+this.renderOptions_={colorScheme:this.colors_,strokeColor:null,strokeWidth:this.attr_("strokeWidth"),axisLabelFontSize:14,axisLineWidth:Dygraph.AXIS_LINE_WIDTH};
+MochiKit.Base.update(this.renderOptions_,this.attrs_);
+MochiKit.Base.update(this.renderOptions_,this.user_attrs_);
+this.plotter_=new DygraphCanvasRenderer(this.hidden_,this.layout_,this.renderOptions_);
 this.createStatusMessage_();
 this.createRollInterface_();
 this.createDragInterface_();
 this.start_();
 };
 this.createStatusMessage_();
 this.createRollInterface_();
 this.createDragInterface_();
 this.start_();
 };
-DateGraph.prototype.rollPeriod=function(){
+Dygraph.prototype.attr_=function(_57){
+if(typeof (this.user_attrs_[_57])!="undefined"){
+return this.user_attrs_[_57];
+}else{
+if(typeof (this.attrs_[_57])!="undefined"){
+return this.attrs_[_57];
+}else{
+return null;
+}
+}
+};
+Dygraph.prototype.log=function(_58,_59){
+if(typeof (console)!="undefined"){
+switch(_58){
+case Dygraph.DEBUG:
+console.debug("dygraphs: "+_59);
+break;
+case Dygraph.INFO:
+console.info("dygraphs: "+_59);
+break;
+case Dygraph.WARNING:
+console.warn("dygraphs: "+_59);
+break;
+case Dygraph.ERROR:
+console.error("dygraphs: "+_59);
+break;
+}
+}
+};
+Dygraph.prototype.info=function(_60){
+this.log(Dygraph.INFO,_60);
+};
+Dygraph.prototype.warn=function(_61){
+this.log(Dygraph.WARNING,_61);
+};
+Dygraph.prototype.error=function(_62){
+this.log(Dygraph.ERROR,_62);
+};
+Dygraph.prototype.rollPeriod=function(){
 return this.rollPeriod_;
 };
 return this.rollPeriod_;
 };
-DateGraph.prototype.createInterface_=function(){
-var _55=this.maindiv_;
+Dygraph.prototype.createInterface_=function(){
+var _63=this.maindiv_;
 this.graphDiv=MochiKit.DOM.DIV({style:{"width":this.width_+"px","height":this.height_+"px"}});
 this.graphDiv=MochiKit.DOM.DIV({style:{"width":this.width_+"px","height":this.height_+"px"}});
-appendChildNodes(_55,this.graphDiv);
-var _56=MochiKit.DOM.CANVAS;
-this.canvas_=_56({style:{"position":"absolute"},width:this.width_,height:this.height_});
+appendChildNodes(_63,this.graphDiv);
+var _64=MochiKit.DOM.CANVAS;
+this.canvas_=_64({style:{"position":"absolute"},width:this.width_,height:this.height_});
 appendChildNodes(this.graphDiv,this.canvas_);
 this.hidden_=this.createPlotKitCanvas_(this.canvas_);
 connect(this.hidden_,"onmousemove",this,function(e){
 appendChildNodes(this.graphDiv,this.canvas_);
 this.hidden_=this.createPlotKitCanvas_(this.canvas_);
 connect(this.hidden_,"onmousemove",this,function(e){
@@ -4849,494 +4898,512 @@ connect(this.hidden_,"onmouseout",this,function(e){
 this.mouseOut_(e);
 });
 };
 this.mouseOut_(e);
 });
 };
-DateGraph.prototype.createPlotKitCanvas_=function(_58){
+Dygraph.prototype.createPlotKitCanvas_=function(_66){
 var h=document.createElement("canvas");
 h.style.position="absolute";
 var h=document.createElement("canvas");
 h.style.position="absolute";
-h.style.top=_58.style.top;
-h.style.left=_58.style.left;
+h.style.top=_66.style.top;
+h.style.left=_66.style.left;
 h.width=this.width_;
 h.height=this.height_;
 MochiKit.DOM.appendChildNodes(this.graphDiv,h);
 return h;
 };
 h.width=this.width_;
 h.height=this.height_;
 MochiKit.DOM.appendChildNodes(this.graphDiv,h);
 return h;
 };
-DateGraph.prototype.setColors_=function(_60){
-var num=this.labels_.length;
+Dygraph.prototype.setColors_=function(){
+var num=this.attr_("labels").length-1;
 this.colors_=[];
 this.colors_=[];
-if(!_60.colors){
-var sat=_60.colorSaturation||1;
-var val=_60.colorValue||0.5;
+var _69=this.attr_("colors");
+if(!_69){
+var sat=this.attr_("colorSaturation")||1;
+var val=this.attr_("colorValue")||0.5;
 for(var i=1;i<=num;i++){
 var hue=(1*i/(1+num));
 this.colors_.push(MochiKit.Color.Color.fromHSV(hue,sat,val));
 }
 }else{
 for(var i=0;i<num;i++){
 for(var i=1;i<=num;i++){
 var hue=(1*i/(1+num));
 this.colors_.push(MochiKit.Color.Color.fromHSV(hue,sat,val));
 }
 }else{
 for(var i=0;i<num;i++){
-var _65=_60.colors[i%_60.colors.length];
-this.colors_.push(MochiKit.Color.Color.fromString(_65));
-}
+var _73=_69[i%_69.length];
+this.colors_.push(MochiKit.Color.Color.fromString(_73));
 }
 }
-};
-DateGraph.prototype.createStatusMessage_=function(){
-if(!this.labelsDiv_){
-var _66=this.attrs_.labelsDivWidth;
-var _67={"style":{"position":"absolute","fontSize":"14px","zIndex":10,"width":_66+"px","top":"0px","left":this.width_-_66+"px","background":"white","textAlign":"left","overflow":"hidden"}};
-MochiKit.Base.update(_67["style"],this.attrs_.labelsDivStyles);
-this.labelsDiv_=MochiKit.DOM.DIV(_67);
-MochiKit.DOM.appendChildNodes(this.graphDiv,this.labelsDiv_);
 }
 }
+this.renderOptions_.colorScheme=this.colors_;
+MochiKit.Base.update(this.plotter_.options,this.renderOptions_);
+MochiKit.Base.update(this.layoutOptions_,this.user_attrs_);
+MochiKit.Base.update(this.layoutOptions_,this.attrs_);
 };
 };
-DateGraph.prototype.createRollInterface_=function(){
-var _68=this.plotter_.options.padding;
-if(typeof this.attrs_.showRoller=="undefined"){
-this.attrs_.showRoller=false;
-}
-var _69=this.attrs_.showRoller?"block":"none";
-var _70={"type":"text","size":"2","value":this.rollPeriod_,"style":{"position":"absolute","zIndex":10,"top":(this.height_-25-_68.bottom)+"px","left":(_68.left+1)+"px","display":_69}};
-var _71=MochiKit.DOM.INPUT(_70);
+Dygraph.prototype.createStatusMessage_=function(){
+if(!this.attr_("labelsDiv")){
+var _74=this.attr_("labelsDivWidth");
+var _75={"style":{"position":"absolute","fontSize":"14px","zIndex":10,"width":_74+"px","top":"0px","left":this.width_-_74+"px","background":"white","textAlign":"left","overflow":"hidden"}};
+MochiKit.Base.update(_75["style"],this.attr_("labelsDivStyles"));
+var div=MochiKit.DOM.DIV(_75);
+MochiKit.DOM.appendChildNodes(this.graphDiv,div);
+this.attrs_.labelsDiv=div;
+}
+};
+Dygraph.prototype.createRollInterface_=function(){
+var _76=this.plotter_.options.padding;
+var _77=this.attr_("showRoller")?"block":"none";
+var _78={"type":"text","size":"2","value":this.rollPeriod_,"style":{"position":"absolute","zIndex":10,"top":(this.height_-25-_76.bottom)+"px","left":(_76.left+1)+"px","display":_77}};
+var _79=MochiKit.DOM.INPUT(_78);
 var pa=this.graphDiv;
 var pa=this.graphDiv;
-MochiKit.DOM.appendChildNodes(pa,_71);
-connect(_71,"onchange",this,function(){
-this.adjustRoll(_71.value);
+MochiKit.DOM.appendChildNodes(pa,_79);
+connect(_79,"onchange",this,function(){
+this.adjustRoll(_79.value);
 });
 });
-return _71;
-};
-DateGraph.prototype.createDragInterface_=function(){
-var _73=this;
-var _74=false;
-var _75=null;
-var _76=null;
-var _77=null;
-var _78=null;
-var _79=null;
+return _79;
+};
+Dygraph.prototype.createDragInterface_=function(){
+var _81=this;
+var _82=false;
+var _83=null;
+var _84=null;
+var _85=null;
+var _86=null;
+var _87=null;
 var px=0;
 var py=0;
 var px=0;
 var py=0;
-var _82=function(e){
+var _90=function(e){
 return e.mouse().page.x-px;
 };
 return e.mouse().page.x-px;
 };
-var _83=function(e){
+var _91=function(e){
 return e.mouse().page.y-py;
 };
 return e.mouse().page.y-py;
 };
-connect(this.hidden_,"onmousemove",function(_84){
-if(_74){
-_77=_82(_84);
-_78=_83(_84);
-_73.drawZoomRect_(_75,_77,_79);
-_79=_77;
+connect(this.hidden_,"onmousemove",function(_92){
+if(_82){
+_85=_90(_92);
+_86=_91(_92);
+_81.drawZoomRect_(_83,_85,_87);
+_87=_85;
 }
 });
 }
 });
-connect(this.hidden_,"onmousedown",function(_85){
-_74=true;
-px=PlotKit.Base.findPosX(_73.canvas_);
-py=PlotKit.Base.findPosY(_73.canvas_);
-_75=_82(_85);
-_76=_83(_85);
+connect(this.hidden_,"onmousedown",function(_93){
+_82=true;
+px=PlotKit.Base.findPosX(_81.canvas_);
+py=PlotKit.Base.findPosY(_81.canvas_);
+_83=_90(_93);
+_84=_91(_93);
 });
 });
-connect(document,"onmouseup",this,function(_86){
-if(_74){
-_74=false;
-_75=null;
-_76=null;
+connect(document,"onmouseup",this,function(_94){
+if(_82){
+_82=false;
+_83=null;
+_84=null;
 }
 });
 }
 });
-connect(this.hidden_,"onmouseout",this,function(_87){
-if(_74){
-_77=null;
-_78=null;
+connect(this.hidden_,"onmouseout",this,function(_95){
+if(_82){
+_85=null;
+_86=null;
 }
 });
 }
 });
-connect(this.hidden_,"onmouseup",this,function(_88){
-if(_74){
-_74=false;
-_77=_82(_88);
-_78=_83(_88);
-var _89=Math.abs(_77-_75);
-var _90=Math.abs(_78-_76);
-if(_89<2&&_90<2&&_73.clickCallback_!=null&&_73.lastx_!=undefined){
-_73.clickCallback_(_88,new Date(_73.lastx_));
+connect(this.hidden_,"onmouseup",this,function(_96){
+if(_82){
+_82=false;
+_85=_90(_96);
+_86=_91(_96);
+var _97=Math.abs(_85-_83);
+var _98=Math.abs(_86-_84);
+if(_97<2&&_98<2&&_81.attr_("clickCallback")!=null&&_81.lastx_!=undefined){
+_81.attr_("clickCallback")(_96,new Date(_81.lastx_));
 }
 }
-if(_89>=10){
-_73.doZoom_(Math.min(_75,_77),Math.max(_75,_77));
+if(_97>=10){
+_81.doZoom_(Math.min(_83,_85),Math.max(_83,_85));
 }else{
 }else{
-_73.canvas_.getContext("2d").clearRect(0,0,_73.canvas_.width,_73.canvas_.height);
+_81.canvas_.getContext("2d").clearRect(0,0,_81.canvas_.width,_81.canvas_.height);
 }
 }
-_75=null;
-_76=null;
+_83=null;
+_84=null;
 }
 });
 }
 });
-connect(this.hidden_,"ondblclick",this,function(_91){
-_73.dateWindow_=null;
-_73.drawGraph_(_73.rawData_);
-var _92=_73.rawData_[0][0];
-var _93=_73.rawData_[_73.rawData_.length-1][0];
-if(_73.zoomCallback_){
-_73.zoomCallback_(_92,_93);
+connect(this.hidden_,"ondblclick",this,function(_99){
+_81.dateWindow_=null;
+_81.drawGraph_(_81.rawData_);
+var _100=_81.rawData_[0][0];
+var _101=_81.rawData_[_81.rawData_.length-1][0];
+if(_81.attr_("zoomCallback")){
+_81.attr_("zoomCallback")(_100,_101);
 }
 });
 };
 }
 });
 };
-DateGraph.prototype.drawZoomRect_=function(_94,_95,_96){
+Dygraph.prototype.drawZoomRect_=function(_102,endX,_104){
 var ctx=this.canvas_.getContext("2d");
 var ctx=this.canvas_.getContext("2d");
-if(_96){
-ctx.clearRect(Math.min(_94,_96),0,Math.abs(_94-_96),this.height_);
+if(_104){
+ctx.clearRect(Math.min(_102,_104),0,Math.abs(_102-_104),this.height_);
 }
 }
-if(_95&&_94){
+if(endX&&_102){
 ctx.fillStyle="rgba(128,128,128,0.33)";
 ctx.fillStyle="rgba(128,128,128,0.33)";
-ctx.fillRect(Math.min(_94,_95),0,Math.abs(_95-_94),this.height_);
+ctx.fillRect(Math.min(_102,endX),0,Math.abs(endX-_102),this.height_);
 }
 };
 }
 };
-DateGraph.prototype.doZoom_=function(_97,_98){
-var _99=this.layout_.points;
-var _100=null;
-var _101=null;
-for(var i=0;i<_99.length;i++){
-var cx=_99[i].canvasx;
-var x=_99[i].xval;
-if(cx<_97&&(_100==null||x>_100)){
-_100=x;
+Dygraph.prototype.doZoom_=function(lowX,_106){
+var _107=this.layout_.points;
+var _108=null;
+var _109=null;
+for(var i=0;i<_107.length;i++){
+var cx=_107[i].canvasx;
+var x=_107[i].xval;
+if(cx<lowX&&(_108==null||x>_108)){
+_108=x;
 }
 }
-if(cx>_98&&(_101==null||x<_101)){
-_101=x;
+if(cx>_106&&(_109==null||x<_109)){
+_109=x;
 }
 }
 }
 }
-if(_100==null){
-_100=_99[0].xval;
+if(_108==null){
+_108=_107[0].xval;
 }
 }
-if(_101==null){
-_101=_99[_99.length-1].xval;
+if(_109==null){
+_109=_107[_107.length-1].xval;
 }
 }
-this.dateWindow_=[_100,_101];
+this.dateWindow_=[_108,_109];
 this.drawGraph_(this.rawData_);
 this.drawGraph_(this.rawData_);
-if(this.zoomCallback_){
-this.zoomCallback_(_100,_101);
+if(this.attr_("zoomCallback")){
+this.attr_("zoomCallback")(_108,_109);
 }
 };
 }
 };
-DateGraph.prototype.mouseMove_=function(_103){
-var _104=_103.mouse().page.x-PlotKit.Base.findPosX(this.hidden_);
-var _105=this.layout_.points;
-var _106=-1;
-var _107=-1;
-var _108=1e+100;
+Dygraph.prototype.mouseMove_=function(_111){
+var _112=_111.mouse().page.x-PlotKit.Base.findPosX(this.hidden_);
+var _113=this.layout_.points;
+var _114=-1;
+var _115=-1;
+var _116=1e+100;
 var idx=-1;
 var idx=-1;
-for(var i=0;i<_105.length;i++){
-var dist=Math.abs(_105[i].canvasx-_104);
-if(dist>_108){
+for(var i=0;i<_113.length;i++){
+var dist=Math.abs(_113[i].canvasx-_112);
+if(dist>_116){
 break;
 }
 break;
 }
-_108=dist;
+_116=dist;
 idx=i;
 }
 if(idx>=0){
 idx=i;
 }
 if(idx>=0){
-_106=_105[idx].xval;
+_114=_113[idx].xval;
 }
 }
-if(_104>_105[_105.length-1].canvasx){
-_106=_105[_105.length-1].xval;
+if(_112>_113[_113.length-1].canvasx){
+_114=_113[_113.length-1].xval;
 }
 }
-var _111=[];
-for(var i=0;i<_105.length;i++){
-if(_105[i].xval==_106){
-_111.push(_105[i]);
+var _119=[];
+for(var i=0;i<_113.length;i++){
+if(_113[i].xval==_114){
+_119.push(_113[i]);
 }
 }
 }
 }
-var _112=this.attrs_.highlightCircleSize;
+var _120=this.attr_("highlightCircleSize");
 var ctx=this.canvas_.getContext("2d");
 if(this.previousVerticalX_>=0){
 var px=this.previousVerticalX_;
 var ctx=this.canvas_.getContext("2d");
 if(this.previousVerticalX_>=0){
 var px=this.previousVerticalX_;
-ctx.clearRect(px-_112-1,0,2*_112+2,this.height_);
+ctx.clearRect(px-_120-1,0,2*_120+2,this.height_);
 }
 }
-if(_111.length>0){
-var _104=_111[0].canvasx;
-var _113=this.xValueFormatter_(_106)+":";
+if(_119.length>0){
+var _112=_119[0].canvasx;
+var _121=this.attr_("xValueFormatter")(_114,this)+":";
 var clen=this.colors_.length;
 var clen=this.colors_.length;
-for(var i=0;i<_111.length;i++){
-if(this.labelsSeparateLines){
-_113+="<br/>";
+for(var i=0;i<_119.length;i++){
+if(this.attr_("labelsSeparateLines")){
+_121+="<br/>";
 }
 }
-var _115=_111[i];
-_113+=" <b><font color='"+this.colors_[i%clen].toHexString()+"'>"+_115.name+"</font></b>:"+this.round_(_115.yval,2);
+var _123=_119[i];
+_121+=" <b><font color='"+this.colors_[i%clen].toHexString()+"'>"+_123.name+"</font></b>:"+this.round_(_123.yval,2);
 }
 }
-this.labelsDiv_.innerHTML=_113;
-this.lastx_=_106;
+this.attr_("labelsDiv").innerHTML=_121;
+this.lastx_=_114;
 ctx.save();
 ctx.save();
-for(var i=0;i<_111.length;i++){
+for(var i=0;i<_119.length;i++){
 ctx.beginPath();
 ctx.fillStyle=this.colors_[i%clen].toRGBString();
 ctx.beginPath();
 ctx.fillStyle=this.colors_[i%clen].toRGBString();
-ctx.arc(_104,_111[i%clen].canvasy,_112,0,360,false);
+ctx.arc(_112,_119[i%clen].canvasy,_120,0,360,false);
 ctx.fill();
 }
 ctx.restore();
 ctx.fill();
 }
 ctx.restore();
-this.previousVerticalX_=_104;
+this.previousVerticalX_=_112;
 }
 };
 }
 };
-DateGraph.prototype.mouseOut_=function(_116){
+Dygraph.prototype.mouseOut_=function(_124){
 var ctx=this.canvas_.getContext("2d");
 ctx.clearRect(0,0,this.width_,this.height_);
 var ctx=this.canvas_.getContext("2d");
 ctx.clearRect(0,0,this.width_,this.height_);
-this.labelsDiv_.innerHTML="";
+this.attr_("labelsDiv").innerHTML="";
 };
 };
-DateGraph.zeropad=function(x){
+Dygraph.zeropad=function(x){
 if(x<10){
 return "0"+x;
 }else{
 return ""+x;
 }
 };
 if(x<10){
 return "0"+x;
 }else{
 return ""+x;
 }
 };
-DateGraph.prototype.hmsString_=function(date){
-var _118=DateGraph.zeropad;
+Dygraph.prototype.hmsString_=function(date){
+var _126=Dygraph.zeropad;
 var d=new Date(date);
 if(d.getSeconds()){
 var d=new Date(date);
 if(d.getSeconds()){
-return _118(d.getHours())+":"+_118(d.getMinutes())+":"+_118(d.getSeconds());
+return _126(d.getHours())+":"+_126(d.getMinutes())+":"+_126(d.getSeconds());
 }else{
 if(d.getMinutes()){
 }else{
 if(d.getMinutes()){
-return _118(d.getHours())+":"+_118(d.getMinutes());
+return _126(d.getHours())+":"+_126(d.getMinutes());
 }else{
 }else{
-return _118(d.getHours());
+return _126(d.getHours());
 }
 }
 };
 }
 }
 };
-DateGraph.prototype.dateString_=function(date){
-var _120=DateGraph.zeropad;
+Dygraph.dateString_=function(date,self){
+var _129=Dygraph.zeropad;
 var d=new Date(date);
 var year=""+d.getFullYear();
 var d=new Date(date);
 var year=""+d.getFullYear();
-var _122=_120(d.getMonth()+1);
-var day=_120(d.getDate());
+var _131=_129(d.getMonth()+1);
+var day=_129(d.getDate());
 var ret="";
 var frac=d.getHours()*3600+d.getMinutes()*60+d.getSeconds();
 if(frac){
 var ret="";
 var frac=d.getHours()*3600+d.getMinutes()*60+d.getSeconds();
 if(frac){
-ret=" "+this.hmsString_(date);
+ret=" "+self.hmsString_(date);
 }
 }
-return year+"/"+_122+"/"+day+ret;
+return year+"/"+_131+"/"+day+ret;
 };
 };
-DateGraph.prototype.round_=function(num,_126){
-var _127=Math.pow(10,_126);
-return Math.round(num*_127)/_127;
+Dygraph.prototype.round_=function(num,_135){
+var _136=Math.pow(10,_135);
+return Math.round(num*_136)/_136;
 };
 };
-DateGraph.prototype.loadedEvent_=function(data){
+Dygraph.prototype.loadedEvent_=function(data){
 this.rawData_=this.parseCSV_(data);
 this.drawGraph_(this.rawData_);
 };
 this.rawData_=this.parseCSV_(data);
 this.drawGraph_(this.rawData_);
 };
-DateGraph.prototype.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
-DateGraph.prototype.quarters=["Jan","Apr","Jul","Oct"];
-DateGraph.prototype.addXTicks_=function(){
-var _129,endDate;
+Dygraph.prototype.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
+Dygraph.prototype.quarters=["Jan","Apr","Jul","Oct"];
+Dygraph.prototype.addXTicks_=function(){
+var _138,endDate;
 if(this.dateWindow_){
 if(this.dateWindow_){
-_129=this.dateWindow_[0];
+_138=this.dateWindow_[0];
 endDate=this.dateWindow_[1];
 }else{
 endDate=this.dateWindow_[1];
 }else{
-_129=this.rawData_[0][0];
+_138=this.rawData_[0][0];
 endDate=this.rawData_[this.rawData_.length-1][0];
 }
 endDate=this.rawData_[this.rawData_.length-1][0];
 }
-var _130=this.xTicker_(_129,endDate);
-this.layout_.updateOptions({xTicks:_130});
-};
-DateGraph.SECONDLY=0;
-DateGraph.TEN_SECONDLY=1;
-DateGraph.THIRTY_SECONDLY=2;
-DateGraph.MINUTELY=3;
-DateGraph.TEN_MINUTELY=4;
-DateGraph.THIRTY_MINUTELY=5;
-DateGraph.HOURLY=6;
-DateGraph.SIX_HOURLY=7;
-DateGraph.DAILY=8;
-DateGraph.WEEKLY=9;
-DateGraph.MONTHLY=10;
-DateGraph.QUARTERLY=11;
-DateGraph.BIANNUAL=12;
-DateGraph.ANNUAL=13;
-DateGraph.DECADAL=14;
-DateGraph.NUM_GRANULARITIES=15;
-DateGraph.SHORT_SPACINGS=[];
-DateGraph.SHORT_SPACINGS[DateGraph.SECONDLY]=1000*1;
-DateGraph.SHORT_SPACINGS[DateGraph.TEN_SECONDLY]=1000*10;
-DateGraph.SHORT_SPACINGS[DateGraph.THIRTY_SECONDLY]=1000*30;
-DateGraph.SHORT_SPACINGS[DateGraph.MINUTELY]=1000*60;
-DateGraph.SHORT_SPACINGS[DateGraph.TEN_MINUTELY]=1000*60*10;
-DateGraph.SHORT_SPACINGS[DateGraph.THIRTY_MINUTELY]=1000*60*30;
-DateGraph.SHORT_SPACINGS[DateGraph.HOURLY]=1000*3600;
-DateGraph.SHORT_SPACINGS[DateGraph.HOURLY]=1000*3600*6;
-DateGraph.SHORT_SPACINGS[DateGraph.DAILY]=1000*86400;
-DateGraph.SHORT_SPACINGS[DateGraph.WEEKLY]=1000*604800;
-DateGraph.prototype.NumXTicks=function(_131,_132,_133){
-if(_133<DateGraph.MONTHLY){
-var _134=DateGraph.SHORT_SPACINGS[_133];
-return Math.floor(0.5+1*(_132-_131)/_134);
-}else{
-var _135=1;
-var _136=12;
-if(_133==DateGraph.QUARTERLY){
-_136=3;
-}
-if(_133==DateGraph.BIANNUAL){
-_136=2;
-}
-if(_133==DateGraph.ANNUAL){
-_136=1;
-}
-if(_133==DateGraph.DECADAL){
-_136=1;
-_135=10;
-}
-var _137=365.2524*24*3600*1000;
-var _138=1*(_132-_131)/_137;
-return Math.floor(0.5+1*_138*_136/_135);
-}
-};
-DateGraph.prototype.GetXAxis=function(_139,_140,_141){
-var _142=[];
-if(_141<DateGraph.MONTHLY){
-var _143=DateGraph.SHORT_SPACINGS[_141];
-var _144="%d%b";
-if(_141<DateGraph.HOURLY){
-_139=_143*Math.floor(0.5+_139/_143);
-}
-for(var t=_139;t<=_140;t+=_143){
+var _139=this.attr_("xTicker")(_138,endDate,this);
+this.layout_.updateOptions({xTicks:_139});
+};
+Dygraph.SECONDLY=0;
+Dygraph.TEN_SECONDLY=1;
+Dygraph.THIRTY_SECONDLY=2;
+Dygraph.MINUTELY=3;
+Dygraph.TEN_MINUTELY=4;
+Dygraph.THIRTY_MINUTELY=5;
+Dygraph.HOURLY=6;
+Dygraph.SIX_HOURLY=7;
+Dygraph.DAILY=8;
+Dygraph.WEEKLY=9;
+Dygraph.MONTHLY=10;
+Dygraph.QUARTERLY=11;
+Dygraph.BIANNUAL=12;
+Dygraph.ANNUAL=13;
+Dygraph.DECADAL=14;
+Dygraph.NUM_GRANULARITIES=15;
+Dygraph.SHORT_SPACINGS=[];
+Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]=1000*1;
+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.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.HOURLY]=1000*3600*6;
+Dygraph.SHORT_SPACINGS[Dygraph.DAILY]=1000*86400;
+Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]=1000*604800;
+Dygraph.prototype.NumXTicks=function(_140,_141,_142){
+if(_142<Dygraph.MONTHLY){
+var _143=Dygraph.SHORT_SPACINGS[_142];
+return Math.floor(0.5+1*(_141-_140)/_143);
+}else{
+var _144=1;
+var _145=12;
+if(_142==Dygraph.QUARTERLY){
+_145=3;
+}
+if(_142==Dygraph.BIANNUAL){
+_145=2;
+}
+if(_142==Dygraph.ANNUAL){
+_145=1;
+}
+if(_142==Dygraph.DECADAL){
+_145=1;
+_144=10;
+}
+var _146=365.2524*24*3600*1000;
+var _147=1*(_141-_140)/_146;
+return Math.floor(0.5+1*_147*_145/_144);
+}
+};
+Dygraph.prototype.GetXAxis=function(_148,_149,_150){
+var _151=[];
+if(_150<Dygraph.MONTHLY){
+var _152=Dygraph.SHORT_SPACINGS[_150];
+var _153="%d%b";
+if(_150<Dygraph.HOURLY){
+_148=_152*Math.floor(0.5+_148/_152);
+}
+for(var t=_148;t<=_149;t+=_152){
 var d=new Date(t);
 var frac=d.getHours()*3600+d.getMinutes()*60+d.getSeconds();
 var d=new Date(t);
 var frac=d.getHours()*3600+d.getMinutes()*60+d.getSeconds();
-if(frac==0||_141>=DateGraph.DAILY){
-_142.push({v:t,label:new Date(t+3600*1000).strftime(_144)});
+if(frac==0||_150>=Dygraph.DAILY){
+_151.push({v:t,label:new Date(t+3600*1000).strftime(_153)});
 }else{
 }else{
-_142.push({v:t,label:this.hmsString_(t)});
+_151.push({v:t,label:this.hmsString_(t)});
 }
 }
 }else{
 }
 }
 }else{
-var _146;
-var _147=1;
-if(_141==DateGraph.MONTHLY){
-_146=[0,1,2,3,4,5,6,7,8,9,10,11,12];
+var _155;
+var _156=1;
+if(_150==Dygraph.MONTHLY){
+_155=[0,1,2,3,4,5,6,7,8,9,10,11,12];
 }else{
 }else{
-if(_141==DateGraph.QUARTERLY){
-_146=[0,3,6,9];
+if(_150==Dygraph.QUARTERLY){
+_155=[0,3,6,9];
 }else{
 }else{
-if(_141==DateGraph.BIANNUAL){
-_146=[0,6];
+if(_150==Dygraph.BIANNUAL){
+_155=[0,6];
 }else{
 }else{
-if(_141==DateGraph.ANNUAL){
-_146=[0];
+if(_150==Dygraph.ANNUAL){
+_155=[0];
 }else{
 }else{
-if(_141==DateGraph.DECADAL){
-_146=[0];
-_147=10;
+if(_150==Dygraph.DECADAL){
+_155=[0];
+_156=10;
 }
 }
 }
 }
 }
 }
 }
 }
 }
 }
-var _148=new Date(_139).getFullYear();
-var _149=new Date(_140).getFullYear();
-var _150=DateGraph.zeropad;
-for(var i=_148;i<=_149;i++){
-if(i%_147!=0){
+var _157=new Date(_148).getFullYear();
+var _158=new Date(_149).getFullYear();
+var _159=Dygraph.zeropad;
+for(var i=_157;i<=_158;i++){
+if(i%_156!=0){
 continue;
 }
 continue;
 }
-for(var j=0;j<_146.length;j++){
-var _151=i+"/"+_150(1+_146[j])+"/01";
-var t=Date.parse(_151);
-if(t<_139||t>_140){
+for(var j=0;j<_155.length;j++){
+var _160=i+"/"+_159(1+_155[j])+"/01";
+var t=Date.parse(_160);
+if(t<_148||t>_149){
 continue;
 }
 continue;
 }
-_142.push({v:t,label:new Date(t).strftime("%b %y")});
+_151.push({v:t,label:new Date(t).strftime("%b %y")});
 }
 }
 }
 }
 }
 }
-return _142;
+return _151;
 };
 };
-DateGraph.prototype.dateTicker=function(_152,_153){
-var _154=-1;
-for(var i=0;i<DateGraph.NUM_GRANULARITIES;i++){
-var _155=this.NumXTicks(_152,_153,i);
-if(this.width_/_155>=this.attrs_.pixelsPerXLabel){
-_154=i;
+Dygraph.dateTicker=function(_161,_162,self){
+var _163=-1;
+for(var i=0;i<Dygraph.NUM_GRANULARITIES;i++){
+var _164=self.NumXTicks(_161,_162,i);
+if(self.width_/_164>=self.attr_("pixelsPerXLabel")){
+_163=i;
 break;
 }
 }
 break;
 }
 }
-if(_154>=0){
-return this.GetXAxis(_152,_153,_154);
+if(_163>=0){
+return self.GetXAxis(_161,_162,_163);
 }else{
 }
 };
 }else{
 }
 };
-DateGraph.prototype.numericTicks=function(minV,maxV){
-var _158=[1,2,5];
-var _159,low_val,high_val,nTicks;
+Dygraph.numericTicks=function(minV,maxV,self){
+var _167=[1,2,5];
+var _168,low_val,high_val,nTicks;
+var _169=self.attr_("pixelsPerYLabel");
 for(var i=-10;i<50;i++){
 for(var i=-10;i<50;i++){
-var _160=Math.pow(10,i);
-for(var j=0;j<_158.length;j++){
-_159=_160*_158[j];
-low_val=Math.floor(minV/_159)*_159;
-high_val=Math.ceil(maxV/_159)*_159;
-nTicks=(high_val-low_val)/_159;
-var _161=this.height_/nTicks;
-if(_161>this.attrs_.pixelsPerYLabel){
+var _170=Math.pow(10,i);
+for(var j=0;j<_167.length;j++){
+_168=_170*_167[j];
+low_val=Math.floor(minV/_168)*_168;
+high_val=Math.ceil(maxV/_168)*_168;
+nTicks=(high_val-low_val)/_168;
+var _171=self.height_/nTicks;
+if(_171>_169){
 break;
 }
 }
 break;
 }
 }
-if(_161>this.attrs_.pixelsPerYLabel){
+if(_171>_169){
 break;
 }
 }
 break;
 }
 }
-var _162=[];
+var _172=[];
 for(var i=0;i<nTicks;i++){
 for(var i=0;i<nTicks;i++){
-var _163=low_val+i*_159;
-var _164=this.round_(_163,2);
-if(this.labelsKMB_){
+var _173=low_val+i*_168;
+var _174=self.round_(_173,2);
+if(self.attr_("labelsKMB")){
 var k=1000;
 var k=1000;
-if(_163>=k*k*k){
-_164=this.round_(_163/(k*k*k),1)+"B";
+if(_173>=k*k*k){
+_174=self.round_(_173/(k*k*k),1)+"B";
 }else{
 }else{
-if(_163>=k*k){
-_164=this.round_(_163/(k*k),1)+"M";
+if(_173>=k*k){
+_174=self.round_(_173/(k*k),1)+"M";
 }else{
 }else{
-if(_163>=k){
-_164=this.round_(_163/k,1)+"K";
+if(_173>=k){
+_174=self.round_(_173/k,1)+"K";
 }
 }
 }
 }
 }
 }
 }
 }
-_162.push({label:_164,v:_163});
+_172.push({label:_174,v:_173});
 }
 }
-return _162;
+return _172;
 };
 };
-DateGraph.prototype.addYTicks_=function(minY,maxY){
-var _168=this.numericTicks(minY,maxY);
-this.layout_.updateOptions({yAxis:[minY,maxY],yTicks:_168});
+Dygraph.prototype.addYTicks_=function(minY,maxY){
+var _178=Dygraph.numericTicks(minY,maxY,this);
+this.layout_.updateOptions({yAxis:[minY,maxY],yTicks:_178});
 };
 };
-DateGraph.prototype.drawGraph_=function(data){
+Dygraph.prototype.drawGraph_=function(data){
 var maxY=null;
 this.layout_.removeAllDatasets();
 var maxY=null;
 this.layout_.removeAllDatasets();
+this.setColors_();
 for(var i=1;i<data[0].length;i++){
 for(var i=1;i<data[0].length;i++){
-var _169=[];
+var _179=[];
 for(var j=0;j<data.length;j++){
 var date=data[j][0];
 for(var j=0;j<data.length;j++){
 var date=data[j][0];
-_169[j]=[date,data[j][i]];
+_179[j]=[date,data[j][i]];
 }
 }
-_169=this.rollingAverage(_169,this.rollPeriod_);
-var bars=this.errorBars_||this.customBars_;
+_179=this.rollingAverage(_179,this.rollPeriod_);
+var bars=this.attr_("errorBars")||this.customBars_;
 if(this.dateWindow_){
 var low=this.dateWindow_[0];
 var high=this.dateWindow_[1];
 if(this.dateWindow_){
 var low=this.dateWindow_[0];
 var high=this.dateWindow_[1];
-var _173=[];
-for(var k=0;k<_169.length;k++){
-if(_169[k][0]>=low&&_169[k][0]<=high){
-_173.push(_169[k]);
-var y=bars?_169[k][1][0]:_169[k][1];
+var _183=[];
+for(var k=0;k<_179.length;k++){
+if(_179[k][0]>=low&&_179[k][0]<=high){
+_183.push(_179[k]);
+var y=bars?_179[k][1][0]:_179[k][1];
 if(maxY==null||y>maxY){
 maxY=y;
 }
 }
 }
 if(maxY==null||y>maxY){
 maxY=y;
 }
 }
 }
-_169=_173;
+_179=_183;
+}else{
+if(!this.customBars_){
+for(var j=0;j<_179.length;j++){
+var y=bars?_179[j][1][0]:_179[j][1];
+if(maxY==null||y>maxY){
+maxY=bars?y+_179[j][1][1]:y;
+}
+}
 }else{
 }else{
-for(var j=0;j<_169.length;j++){
-var y=bars?_169[j][1][0]:_169[j][1];
+for(var j=0;j<_179.length;j++){
+var y=_179[j][1][0];
+var high=_179[j][1][2];
+if(high>y){
+y=high;
+}
 if(maxY==null||y>maxY){
 if(maxY==null||y>maxY){
-maxY=bars?y+_169[j][1][1]:y;
+maxY=y;
+}
 }
 }
 }
 if(bars){
 var vals=[];
 }
 }
 }
 if(bars){
 var vals=[];
-for(var j=0;j<_169.length;j++){
-vals[j]=[_169[j][0],_169[j][1][0],_169[j][1][1],_169[j][1][2]];
+for(var j=0;j<_179.length;j++){
+vals[j]=[_179[j][0],_179[j][1][0],_179[j][1][1],_179[j][1][2]];
 }
 }
-this.layout_.addDataset(this.labels_[i-1],vals);
+this.layout_.addDataset(this.attr_("labels")[i],vals);
 }else{
 }else{
-this.layout_.addDataset(this.labels_[i-1],_169);
+this.layout_.addDataset(this.attr_("labels")[i],_179);
 }
 }
 if(this.valueRange_!=null){
 }
 }
 if(this.valueRange_!=null){
@@ -5354,44 +5421,44 @@ this.plotter_.clear();
 this.plotter_.render();
 this.canvas_.getContext("2d").clearRect(0,0,this.canvas_.width,this.canvas_.height);
 };
 this.plotter_.render();
 this.canvas_.getContext("2d").clearRect(0,0,this.canvas_.width,this.canvas_.height);
 };
-DateGraph.prototype.rollingAverage=function(_175,_176){
-if(_175.length<2){
-return _175;
+Dygraph.prototype.rollingAverage=function(_185,_186){
+if(_185.length<2){
+return _185;
 }
 }
-var _176=Math.min(_176,_175.length-1);
-var _177=[];
-var _178=this.sigma_;
+var _186=Math.min(_186,_185.length-1);
+var _187=[];
+var _188=this.attr_("sigma");
 if(this.fractions_){
 var num=0;
 var den=0;
 var mult=100;
 if(this.fractions_){
 var num=0;
 var den=0;
 var mult=100;
-for(var i=0;i<_175.length;i++){
-num+=_175[i][1][0];
-den+=_175[i][1][1];
-if(i-_176>=0){
-num-=_175[i-_176][1][0];
-den-=_175[i-_176][1][1];
-}
-var date=_175[i][0];
-var _181=den?num/den:0;
-if(this.errorBars_){
+for(var i=0;i<_185.length;i++){
+num+=_185[i][1][0];
+den+=_185[i][1][1];
+if(i-_186>=0){
+num-=_185[i-_186][1][0];
+den-=_185[i-_186][1][1];
+}
+var date=_185[i][0];
+var _191=den?num/den:0;
+if(this.attr_("errorBars")){
 if(this.wilsonInterval_){
 if(den){
 if(this.wilsonInterval_){
 if(den){
-var p=_181<0?0:_181,n=den;
-var pm=_178*Math.sqrt(p*(1-p)/n+_178*_178/(4*n*n));
-var _184=1+_178*_178/den;
-var low=(p+_178*_178/(2*den)-pm)/_184;
-var high=(p+_178*_178/(2*den)+pm)/_184;
-_177[i]=[date,[p*mult,(p-low)*mult,(high-p)*mult]];
+var p=_191<0?0:_191,n=den;
+var pm=_188*Math.sqrt(p*(1-p)/n+_188*_188/(4*n*n));
+var _194=1+_188*_188/den;
+var low=(p+_188*_188/(2*den)-pm)/_194;
+var high=(p+_188*_188/(2*den)+pm)/_194;
+_187[i]=[date,[p*mult,(p-low)*mult,(high-p)*mult]];
 }else{
 }else{
-_177[i]=[date,[0,0,0]];
+_187[i]=[date,[0,0,0]];
 }
 }else{
 }
 }else{
-var _185=den?_178*Math.sqrt(_181*(1-_181)/den):1;
-_177[i]=[date,[mult*_181,mult*_185,mult*_185]];
+var _195=den?_188*Math.sqrt(_191*(1-_191)/den):1;
+_187[i]=[date,[mult*_191,mult*_195,mult*_195]];
 }
 }else{
 }
 }else{
-_177[i]=[date,mult*_181];
+_187[i]=[date,mult*_191];
 }
 }
 }else{
 }
 }
 }else{
@@ -5399,157 +5466,240 @@ if(this.customBars_){
 var low=0;
 var mid=0;
 var high=0;
 var low=0;
 var mid=0;
 var high=0;
-var _187=0;
-for(var i=0;i<_175.length;i++){
-var data=_175[i][1];
+var _197=0;
+for(var i=0;i<_185.length;i++){
+var data=_185[i][1];
 var y=data[1];
 var y=data[1];
-_177[i]=[_175[i][0],[y,y-data[0],data[2]-y]];
+_187[i]=[_185[i][0],[y,y-data[0],data[2]-y]];
 low+=data[0];
 mid+=y;
 high+=data[2];
 low+=data[0];
 mid+=y;
 high+=data[2];
-_187+=1;
-if(i-_176>=0){
-var prev=_175[i-_176];
+_197+=1;
+if(i-_186>=0){
+var prev=_185[i-_186];
 low-=prev[1][0];
 mid-=prev[1][1];
 high-=prev[1][2];
 low-=prev[1][0];
 mid-=prev[1][1];
 high-=prev[1][2];
-_187-=1;
+_197-=1;
 }
 }
-_177[i]=[_175[i][0],[1*mid/_187,1*(mid-low)/_187,1*(high-mid)/_187]];
+_187[i]=[_185[i][0],[1*mid/_197,1*(mid-low)/_197,1*(high-mid)/_197]];
 }
 }else{
 }
 }else{
-var _189=Math.min(_176-1,_175.length-2);
-if(!this.errorBars_){
-for(var i=0;i<_189;i++){
+var _199=Math.min(_186-1,_185.length-2);
+if(!this.attr_("errorBars")){
+for(var i=0;i<_199;i++){
 var sum=0;
 for(var j=0;j<i+1;j++){
 var sum=0;
 for(var j=0;j<i+1;j++){
-sum+=_175[j][1];
+sum+=_185[j][1];
 }
 }
-_177[i]=[_175[i][0],sum/(i+1)];
+_187[i]=[_185[i][0],sum/(i+1)];
 }
 }
-for(var i=Math.min(_176-1,_175.length-2);i<_175.length;i++){
+for(var i=Math.min(_186-1,_185.length-2);i<_185.length;i++){
 var sum=0;
 var sum=0;
-for(var j=i-_176+1;j<i+1;j++){
-sum+=_175[j][1];
+for(var j=i-_186+1;j<i+1;j++){
+sum+=_185[j][1];
 }
 }
-_177[i]=[_175[i][0],sum/_176];
+_187[i]=[_185[i][0],sum/_186];
 }
 }else{
 }
 }else{
-for(var i=0;i<_189;i++){
+for(var i=0;i<_199;i++){
 var sum=0;
 var sum=0;
-var _191=0;
+var _201=0;
 for(var j=0;j<i+1;j++){
 for(var j=0;j<i+1;j++){
-sum+=_175[j][1][0];
-_191+=Math.pow(_175[j][1][1],2);
+sum+=_185[j][1][0];
+_201+=Math.pow(_185[j][1][1],2);
 }
 }
-var _185=Math.sqrt(_191)/(i+1);
-_177[i]=[_175[i][0],[sum/(i+1),_178*_185,_178*_185]];
+var _195=Math.sqrt(_201)/(i+1);
+_187[i]=[_185[i][0],[sum/(i+1),_188*_195,_188*_195]];
 }
 }
-for(var i=Math.min(_176-1,_175.length-2);i<_175.length;i++){
+for(var i=Math.min(_186-1,_185.length-2);i<_185.length;i++){
 var sum=0;
 var sum=0;
-var _191=0;
-for(var j=i-_176+1;j<i+1;j++){
-sum+=_175[j][1][0];
-_191+=Math.pow(_175[j][1][1],2);
+var _201=0;
+for(var j=i-_186+1;j<i+1;j++){
+sum+=_185[j][1][0];
+_201+=Math.pow(_185[j][1][1],2);
 }
 }
-var _185=Math.sqrt(_191)/_176;
-_177[i]=[_175[i][0],[sum/_176,_178*_185,_178*_185]];
+var _195=Math.sqrt(_201)/_186;
+_187[i]=[_185[i][0],[sum/_186,_188*_195,_188*_195]];
 }
 }
 }
 }
 }
 }
 }
 }
-return _177;
+return _187;
 };
 };
-DateGraph.prototype.dateParser=function(_192){
-var _193;
-if(_192.length==10&&_192.search("-")!=-1){
-_193=_192.replace("-","/","g");
-while(_193.search("-")!=-1){
-_193=_193.replace("-","/");
+Dygraph.dateParser=function(_202,self){
+var _203;
+var d;
+if(_202.length==10&&_202.search("-")!=-1){
+_203=_202.replace("-","/","g");
+while(_203.search("-")!=-1){
+_203=_203.replace("-","/");
 }
 }
-return Date.parse(_193);
+d=Date.parse(_203);
 }else{
 }else{
-if(_192.length==8){
-_193=_192.substr(0,4)+"/"+_192.substr(4,2)+"/"+_192.substr(6,2);
-return Date.parse(_193);
+if(_202.length==8){
+_203=_202.substr(0,4)+"/"+_202.substr(4,2)+"/"+_202.substr(6,2);
+d=Date.parse(_203);
 }else{
 }else{
-return Date.parse(_192);
+d=Date.parse(_202);
+}
 }
 }
+if(!d||isNaN(d)){
+self.error("Couldn't parse "+_202+" as a date");
 }
 }
+return d;
 };
 };
-DateGraph.prototype.parseCSV_=function(data){
+Dygraph.prototype.detectTypeFromString_=function(str){
+var _205=false;
+if(str.indexOf("-")>=0||str.indexOf("/")>=0||isNaN(parseFloat(str))){
+_205=true;
+}else{
+if(str.length==8&&str>"19700101"&&str<"20371231"){
+_205=true;
+}
+}
+if(_205){
+this.attrs_.xValueFormatter=Dygraph.dateString_;
+this.attrs_.xValueParser=Dygraph.dateParser;
+this.attrs_.xTicker=Dygraph.dateTicker;
+}else{
+this.attrs_.xValueFormatter=function(x){
+return x;
+};
+this.attrs_.xValueParser=function(x){
+return parseFloat(x);
+};
+this.attrs_.xTicker=Dygraph.numericTicks;
+}
+};
+Dygraph.prototype.parseCSV_=function(data){
 var ret=[];
 var ret=[];
-var _194=data.split("\n");
-var _195=this.labelsFromCSV_?1:0;
+var _206=data.split("\n");
+var _207=0;
 if(this.labelsFromCSV_){
 if(this.labelsFromCSV_){
-var _196=_194[0].split(",");
-_196.shift();
-this.labels_=_196;
-this.setColors_(this.attrs_);
-this.renderOptions_.colorScheme=this.colors_;
-MochiKit.Base.update(this.plotter_.options,this.renderOptions_);
-MochiKit.Base.update(this.layoutOptions_,this.attrs_);
-}
-for(var i=_195;i<_194.length;i++){
-var line=_194[i];
+_207=1;
+this.attrs_.labels=_206[0].split(",");
+}
+var _208;
+var _209=false;
+var _210=this.attr_("labels").length;
+for(var i=_207;i<_206.length;i++){
+var line=_206[i];
 if(line.length==0){
 continue;
 }
 if(line.length==0){
 continue;
 }
-var _198=line.split(",");
-if(_198.length<2){
+var _212=line.split(",");
+if(_212.length<2){
 continue;
 }
 continue;
 }
-var _199=[];
-_199[0]=this.xValueParser_(_198[0]);
+var _213=[];
+if(!_209){
+this.detectTypeFromString_(_212[0]);
+_208=this.attr_("xValueParser");
+_209=true;
+}
+_213[0]=_208(_212[0],this);
 if(this.fractions_){
 if(this.fractions_){
-for(var j=1;j<_198.length;j++){
-var vals=_198[j].split("/");
-_199[j]=[parseFloat(vals[0]),parseFloat(vals[1])];
+for(var j=1;j<_212.length;j++){
+var vals=_212[j].split("/");
+_213[j]=[parseFloat(vals[0]),parseFloat(vals[1])];
 }
 }else{
 }
 }else{
-if(this.errorBars_){
-for(var j=1;j<_198.length;j+=2){
-_199[(j+1)/2]=[parseFloat(_198[j]),parseFloat(_198[j+1])];
+if(this.attr_("errorBars")){
+for(var j=1;j<_212.length;j+=2){
+_213[(j+1)/2]=[parseFloat(_212[j]),parseFloat(_212[j+1])];
 }
 }else{
 if(this.customBars_){
 }
 }else{
 if(this.customBars_){
-for(var j=1;j<_198.length;j++){
-var vals=_198[j].split(";");
-_199[j]=[parseFloat(vals[0]),parseFloat(vals[1]),parseFloat(vals[2])];
+for(var j=1;j<_212.length;j++){
+var vals=_212[j].split(";");
+_213[j]=[parseFloat(vals[0]),parseFloat(vals[1]),parseFloat(vals[2])];
 }
 }else{
 }
 }else{
-for(var j=1;j<_198.length;j++){
-_199[j]=parseFloat(_198[j]);
+for(var j=1;j<_212.length;j++){
+_213[j]=parseFloat(_212[j]);
+}
 }
 }
 }
 }
 }
 }
+ret.push(_213);
+if(_213.length!=_210){
+this.error("Number of columns in line "+i+" ("+_213.length+") does not agree with number of labels ("+_210+") "+line);
 }
 }
-ret.push(_199);
 }
 return ret;
 };
 }
 return ret;
 };
-DateGraph.prototype.parseDataTable_=function(data){
+Dygraph.prototype.parseArray_=function(data){
+if(data.length==0){
+this.error("Can't plot empty data set");
+return null;
+}
+if(data[0].length==0){
+this.error("Data set cannot contain an empty row");
+return null;
+}
+if(this.attr_("labels")==null){
+this.warn("Using default labels. Set labels explicitly via 'labels' "+"in the options parameter");
+this.attrs_.labels=["X"];
+for(var i=1;i<data[0].length;i++){
+this.attrs_.labels.push("Y"+i);
+}
+}
+if(MochiKit.Base.isDateLike(data[0][0])){
+this.attrs_.xValueFormatter=Dygraph.dateString_;
+this.attrs_.xTicker=Dygraph.dateTicker;
+var _214=MochiKit.Base.clone(data);
+for(var i=0;i<data.length;i++){
+if(_214[i].length==0){
+this.error("Row "<<(1+i)<<" of data is empty");
+return null;
+}
+if(_214[i][0]==null||typeof (_214[i][0].getTime)!="function"){
+this.error("x value in row "<<(1+i)<<" is not a Date");
+return null;
+}
+_214[i][0]=_214[i][0].getTime();
+}
+return _214;
+}else{
+this.attrs_.xValueFormatter=function(x){
+return x;
+};
+this.attrs_.xTicker=Dygraph.numericTicks;
+return data;
+}
+};
+Dygraph.prototype.parseDataTable_=function(data){
 var cols=data.getNumberOfColumns();
 var rows=data.getNumberOfRows();
 var cols=data.getNumberOfColumns();
 var rows=data.getNumberOfRows();
-var _202=[];
+var _217=[];
 for(var i=0;i<cols;i++){
 for(var i=0;i<cols;i++){
-_202.push(data.getColumnLabel(i));
+_217.push(data.getColumnLabel(i));
 }
 }
-_202.shift();
-this.labels_=_202;
-this.setColors_(this.attrs_);
-this.renderOptions_.colorScheme=this.colors_;
-MochiKit.Base.update(this.plotter_.options,this.renderOptions_);
-MochiKit.Base.update(this.layoutOptions_,this.attrs_);
-var _203=data.getColumnType(0);
-if(_203!="date"&&_203!="number"){
-alert("only 'date' and 'number' types are supported for column 1"+"of DataTable input (Got '"+_203+"')");
+this.attrs_.labels=_217;
+var _218=data.getColumnType(0);
+if(_218=="date"){
+this.attrs_.xValueFormatter=Dygraph.dateString_;
+this.attrs_.xValueParser=Dygraph.dateParser;
+this.attrs_.xTicker=Dygraph.dateTicker;
+}else{
+if(_218!="number"){
+this.attrs_.xValueFormatter=function(x){
+return x;
+};
+this.attrs_.xValueParser=function(x){
+return parseFloat(x);
+};
+this.attrs_.xTicker=Dygraph.numericTicks;
+}else{
+this.error("only 'date' and 'number' types are supported for column 1"+"of DataTable input (Got '"+_218+"')");
 return null;
 }
 return null;
 }
+}
 var ret=[];
 for(var i=0;i<rows;i++){
 var row=[];
 var ret=[];
 for(var i=0;i<rows;i++){
 var row=[];
-if(_203=="date"){
+if(_218=="date"){
 row.push(data.getValue(i,0).getTime());
 }else{
 row.push(data.getValue(i,0));
 row.push(data.getValue(i,0).getTime());
 }else{
 row.push(data.getValue(i,0));
@@ -5561,69 +5711,74 @@ ret.push(row);
 }
 return ret;
 };
 }
 return ret;
 };
-DateGraph.prototype.start_=function(){
+Dygraph.prototype.start_=function(){
 if(typeof this.file_=="function"){
 this.loadedEvent_(this.file_());
 }else{
 if(typeof this.file_=="function"){
 this.loadedEvent_(this.file_());
 }else{
+if(MochiKit.Base.isArrayLike(this.file_)){
+this.rawData_=this.parseArray_(this.file_);
+this.drawGraph_(this.rawData_);
+}else{
 if(typeof this.file_=="object"&&typeof this.file_.getColumnRange=="function"){
 this.rawData_=this.parseDataTable_(this.file_);
 this.drawGraph_(this.rawData_);
 }else{
 if(typeof this.file_=="object"&&typeof this.file_.getColumnRange=="function"){
 this.rawData_=this.parseDataTable_(this.file_);
 this.drawGraph_(this.rawData_);
 }else{
+if(typeof this.file_=="string"){
+if(this.file_.indexOf("\n")>=0){
+this.loadedEvent_(this.file_);
+}else{
 var req=new XMLHttpRequest();
 var req=new XMLHttpRequest();
-var _206=this;
+var _221=this;
 req.onreadystatechange=function(){
 if(req.readyState==4){
 if(req.status==200){
 req.onreadystatechange=function(){
 if(req.readyState==4){
 if(req.status==200){
-_206.loadedEvent_(req.responseText);
+_221.loadedEvent_(req.responseText);
 }
 }
 };
 req.open("GET",this.file_,true);
 req.send(null);
 }
 }
 }
 };
 req.open("GET",this.file_,true);
 req.send(null);
 }
+}else{
+this.error("Unknown data format: "+(typeof this.file_));
 }
 }
-};
-DateGraph.prototype.updateOptions=function(_207){
-if(_207.errorBars){
-this.errorBars_=_207.errorBars;
 }
 }
-if(_207.customBars){
-this.customBars_=_207.customBars;
 }
 }
-if(_207.strokeWidth){
-this.strokeWidth_=_207.strokeWidth;
 }
 }
-if(_207.rollPeriod){
-this.rollPeriod_=_207.rollPeriod;
+};
+Dygraph.prototype.updateOptions=function(_222){
+if(_222.customBars){
+this.customBars_=_222.customBars;
 }
 }
-if(_207.dateWindow){
-this.dateWindow_=_207.dateWindow;
+if(_222.rollPeriod){
+this.rollPeriod_=_222.rollPeriod;
 }
 }
-if(_207.valueRange){
-this.valueRange_=_207.valueRange;
+if(_222.dateWindow){
+this.dateWindow_=_222.dateWindow;
 }
 }
-MochiKit.Base.update(this.attrs_,_207);
-if(typeof (_207.labels)!="undefined"){
-this.labels_=_207.labels;
-this.labelsFromCSV_=(_207.labels==null);
+if(_222.valueRange){
+this.valueRange_=_222.valueRange;
 }
 }
-this.layout_.updateOptions({"errorBars":this.errorBars_});
-if(_207["file"]&&_207["file"]!=this.file_){
-this.file_=_207["file"];
+MochiKit.Base.update(this.user_attrs_,_222);
+this.labelsFromCSV_=(this.attr_("labels")==null);
+this.layout_.updateOptions({"errorBars":this.attr_("errorBars")});
+if(_222["file"]&&_222["file"]!=this.file_){
+this.file_=_222["file"];
 this.start_();
 }else{
 this.drawGraph_(this.rawData_);
 }
 };
 this.start_();
 }else{
 this.drawGraph_(this.rawData_);
 }
 };
-DateGraph.prototype.adjustRoll=function(_208){
-this.rollPeriod_=_208;
+Dygraph.prototype.adjustRoll=function(_223){
+this.rollPeriod_=_223;
 this.drawGraph_(this.rawData_);
 };
 this.drawGraph_(this.rawData_);
 };
-DateGraph.GVizChart=function(_209){
-this.container=_209;
+Dygraph.GVizChart=function(_224){
+this.container=_224;
 };
 };
-DateGraph.GVizChart.prototype.draw=function(data,_210){
+Dygraph.GVizChart.prototype.draw=function(data,_225){
 this.container.innerHTML="";
 this.container.innerHTML="";
-this.date_graph=new DateGraph(this.container,data,null,_210||{});
+this.date_graph=new Dygraph(this.container,data,_225);
 };
 };
+DateGraph=Dygraph;
 
 
index 9a948de..1204271 100644 (file)
@@ -3,42 +3,41 @@
 
 /**
  * @fileoverview Creates an interactive, zoomable graph based on a CSV file or
 
 /**
  * @fileoverview Creates an interactive, zoomable graph based on a CSV file or
- * string. DateGraph can handle multiple series with or without error bars. The
- * date/value ranges will be automatically set. DateGraph uses the
+ * string. Dygraph can handle multiple series with or without error bars. The
+ * date/value ranges will be automatically set. Dygraph uses the
  * &lt;canvas&gt; tag, so it only works in FF1.5+.
  * @author danvdk@gmail.com (Dan Vanderkam)
 
   Usage:
    <div id="graphdiv" style="width:800px; height:500px;"></div>
    <script type="text/javascript">
  * &lt;canvas&gt; tag, so it only works in FF1.5+.
  * @author danvdk@gmail.com (Dan Vanderkam)
 
   Usage:
    <div id="graphdiv" style="width:800px; height:500px;"></div>
    <script type="text/javascript">
-     new DateGraph(document.getElementById("graphdiv"),
-                   "datafile.csv",
-                     ["Series 1", "Series 2"],
-                     { }); // options
+     new Dygraph(document.getElementById("graphdiv"),
+                 "datafile.csv",  // CSV file with headers
+                 { }); // options
    </script>
 
  The CSV file is of the form
 
    </script>
 
  The CSV file is of the form
 
+   Date,SeriesA,SeriesB,SeriesC
    YYYYMMDD,A1,B1,C1
    YYYYMMDD,A2,B2,C2
 
    YYYYMMDD,A1,B1,C1
    YYYYMMDD,A2,B2,C2
 
- If null is passed as the third parameter (series names), then the first line
- of the CSV file is assumed to contain names for each series.
-
  If the 'errorBars' option is set in the constructor, the input should be of
  the form
 
  If the 'errorBars' option is set in the constructor, the input should be of
  the form
 
+   Date,SeriesA,SeriesB,...
    YYYYMMDD,A1,sigmaA1,B1,sigmaB1,...
    YYYYMMDD,A2,sigmaA2,B2,sigmaB2,...
 
  If the 'fractions' option is set, the input should be of the form:
 
    YYYYMMDD,A1,sigmaA1,B1,sigmaB1,...
    YYYYMMDD,A2,sigmaA2,B2,sigmaB2,...
 
  If the 'fractions' option is set, the input should be of the form:
 
+   Date,SeriesA,SeriesB,...
    YYYYMMDD,A1/B1,A2/B2,...
    YYYYMMDD,A1/B1,A2/B2,...
 
  And error bars will be calculated automatically using a binomial distribution.
 
    YYYYMMDD,A1/B1,A2/B2,...
    YYYYMMDD,A1/B1,A2/B2,...
 
  And error bars will be calculated automatically using a binomial distribution.
 
- For further documentation and examples, see http://www/~danvk/dg/
+ For further documentation and examples, see http://www.danvk.org/dygraphs
 
  */
 
 
  */
 
  * returns this data. The expected format for each line is
  * YYYYMMDD,val1,val2,... or, if attrs.errorBars is set,
  * YYYYMMDD,val1,stddev1,val2,stddev2,...
  * returns this data. The expected format for each line is
  * YYYYMMDD,val1,val2,... or, if attrs.errorBars is set,
  * YYYYMMDD,val1,stddev1,val2,stddev2,...
- * @param {Array.<String>} labels Labels for the data series
  * @param {Object} attrs Various other attributes, e.g. errorBars determines
  * whether the input data contains error ranges.
  */
  * @param {Object} attrs Various other attributes, e.g. errorBars determines
  * whether the input data contains error ranges.
  */
-DateGraph = function(div, file, labels, attrs) {
-  if (arguments.length > 0)
-    this.__init__(div, file, labels, attrs);
+Dygraph = function(div, data, opts) {
+  if (arguments.length > 0) {
+    if (arguments.length == 4) {
+      // 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.
+      this.warn("Using deprecated four-argument dygraph constructor");
+      this.__old_init__(div, data, arguments[2], arguments[3]);
+    } else {
+      this.__init__(div, data, opts);
+    }
+  }
 };
 
 };
 
-DateGraph.NAME = "DateGraph";
-DateGraph.VERSION = "1.1";
-DateGraph.__repr__ = function() {
+Dygraph.NAME = "Dygraph";
+Dygraph.VERSION = "1.2";
+Dygraph.__repr__ = function() {
   return "[" + this.NAME + " " + this.VERSION + "]";
 };
   return "[" + this.NAME + " " + this.VERSION + "]";
 };
-DateGraph.toString = function() {
+Dygraph.toString = function() {
   return this.__repr__();
 };
 
 // Various default values
   return this.__repr__();
 };
 
 // Various default values
-DateGraph.DEFAULT_ROLL_PERIOD = 1;
-DateGraph.DEFAULT_WIDTH = 480;
-DateGraph.DEFAULT_HEIGHT = 320;
-DateGraph.DEFAULT_STROKE_WIDTH = 1.0;
-DateGraph.AXIS_LINE_WIDTH = 0.3;
+Dygraph.DEFAULT_ROLL_PERIOD = 1;
+Dygraph.DEFAULT_WIDTH = 480;
+Dygraph.DEFAULT_HEIGHT = 320;
+Dygraph.AXIS_LINE_WIDTH = 0.3;
 
 // Default attribute values.
 
 // Default attribute values.
-DateGraph.DEFAULT_ATTRS = {
+Dygraph.DEFAULT_ATTRS = {
   highlightCircleSize: 3,
   pixelsPerXLabel: 60,
   pixelsPerYLabel: 30,
   highlightCircleSize: 3,
   pixelsPerXLabel: 60,
   pixelsPerYLabel: 30,
+
   labelsDivWidth: 250,
   labelsDivStyles: {
     // TODO(danvk): move defaults from createStatusMessage_ here.
   labelsDivWidth: 250,
   labelsDivStyles: {
     // TODO(danvk): move defaults from createStatusMessage_ here.
-  }
+  },
+  labelsSeparateLines: false,
+  labelsKMB: false,
+
+  strokeWidth: 1.0,
 
   // TODO(danvk): default padding
 
   // TODO(danvk): default padding
+
+  showRoller: false,
+  xValueFormatter: Dygraph.dateString_,
+  xValueParser: Dygraph.dateParser,
+  xTicker: Dygraph.dateTicker,
+
+  sigma: 2.0,
+  errorBars: false,
+  fractions: false,
+  wilsonInterval: true,  // only relevant if fractions is true
+  customBars: false
+};
+
+// Various logging levels.
+Dygraph.DEBUG = 1;
+Dygraph.INFO = 2;
+Dygraph.WARNING = 3;
+Dygraph.ERROR = 3;
+
+Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) {
+  // Labels is no longer a constructor parameter, since it's typically set
+  // directly from the data source. It also conains a name for the x-axis,
+  // which the previous constructor form did not.
+  if (labels != null) {
+    var new_labels = ["Date"];
+    for (var i = 0; i < labels.length; i++) new_labels.push(labels[i]);
+    MochiKit.Base.update(attrs, { 'labels': new_labels });
+  }
+  this.__init__(div, file, attrs);
 };
 
 /**
 };
 
 /**
- * Initializes the DateGraph. This creates a new DIV and constructs the PlotKit
+ * Initializes the Dygraph. This creates a new DIV and constructs the PlotKit
  * and interaction &lt;canvas&gt; inside of it. See the constructor for details
  * on the parameters.
  * @param {String | Function} file Source data
  * and interaction &lt;canvas&gt; inside of it. See the constructor for details
  * on the parameters.
  * @param {String | Function} file Source data
@@ -95,70 +135,73 @@ DateGraph.DEFAULT_ATTRS = {
  * @param {Object} attrs Miscellaneous other options
  * @private
  */
  * @param {Object} attrs Miscellaneous other options
  * @private
  */
-DateGraph.prototype.__init__ = function(div, file, labels, attrs) {
+Dygraph.prototype.__init__ = function(div, file, attrs) {
+  // Support two-argument constructor
+  if (attrs == null) { attrs = {}; }
+
   // Copy the important bits into the object
   // TODO(danvk): most of these should just stay in the attrs_ dictionary.
   this.maindiv_ = div;
   // Copy the important bits into the object
   // TODO(danvk): most of these should just stay in the attrs_ dictionary.
   this.maindiv_ = div;
-  this.labels_ = labels;
   this.file_ = file;
   this.file_ = file;
-  this.rollPeriod_ = attrs.rollPeriod || DateGraph.DEFAULT_ROLL_PERIOD;
+  this.rollPeriod_ = attrs.rollPeriod || Dygraph.DEFAULT_ROLL_PERIOD;
   this.previousVerticalX_ = -1;
   this.previousVerticalX_ = -1;
-  this.width_ = parseInt(div.style.width, 10);
-  this.height_ = parseInt(div.style.height, 10);
-  this.errorBars_ = attrs.errorBars || false;
   this.fractions_ = attrs.fractions || false;
   this.fractions_ = attrs.fractions || false;
-  this.strokeWidth_ = attrs.strokeWidth || DateGraph.DEFAULT_STROKE_WIDTH;
   this.dateWindow_ = attrs.dateWindow || null;
   this.valueRange_ = attrs.valueRange || null;
   this.dateWindow_ = attrs.dateWindow || null;
   this.valueRange_ = attrs.valueRange || null;
-  this.labelsSeparateLines = attrs.labelsSeparateLines || false;
-  this.labelsDiv_ = attrs.labelsDiv || null;
-  this.labelsKMB_ = attrs.labelsKMB || false;
-  this.xValueParser_ = attrs.xValueParser || DateGraph.prototype.dateParser;
-  this.xValueFormatter_ = attrs.xValueFormatter ||
-      DateGraph.prototype.dateString_;
-  this.xTicker_ = attrs.xTicker || DateGraph.prototype.dateTicker;
-  this.sigma_ = attrs.sigma || 2.0;
   this.wilsonInterval_ = attrs.wilsonInterval || true;
   this.customBars_ = attrs.customBars || false;
 
   this.wilsonInterval_ = attrs.wilsonInterval || true;
   this.customBars_ = attrs.customBars || false;
 
-  this.attrs_ = {};
-  MochiKit.Base.update(this.attrs_, DateGraph.DEFAULT_ATTRS);
-  MochiKit.Base.update(this.attrs_, attrs);
-
-  if (typeof this.attrs_.pixelsPerXLabel == 'undefined') {
-    this.attrs_.pixelsPerXLabel = 60;
+  // If the div isn't already sized then give it a default size.
+  if (div.style.width == '') {
+    div.style.width = Dygraph.DEFAULT_WIDTH + "px";
+  }
+  if (div.style.height == '') {
+    div.style.height = Dygraph.DEFAULT_HEIGHT + "px";
   }
   }
+  this.width_ = parseInt(div.style.width, 10);
+  this.height_ = parseInt(div.style.height, 10);
 
 
-  // Make a note of whether labels will be pulled from the CSV file.
-  this.labelsFromCSV_ = (this.labels_ == null);
-  if (this.labels_ == null)
-    this.labels_ = [];
+  // Dygraphs has many options, some of which interact with one another.
+  // To keep track of everything, we maintain two sets of options:
+  //
+  //  this.user_attrs_   only options explicitly set by the user. 
+  //  this.attrs_        defaults, options derived from user_attrs_, data.
+  //
+  // Options are then accessed this.attr_('attr'), which first looks at
+  // user_attrs_ and then computed attrs_. This way Dygraphs can set intelligent
+  // defaults without overriding behavior that the user specifically asks for.
+  this.user_attrs_ = {};
+  MochiKit.Base.update(this.user_attrs_, attrs);
 
 
-  // Prototype of the callback is "void clickCallback(event, date)"
-  this.clickCallback_ = attrs.clickCallback || null;
+  this.attrs_ = {};
+  MochiKit.Base.update(this.attrs_, Dygraph.DEFAULT_ATTRS);
 
 
-  // Prototype of zoom callback is "void dragCallback(minDate, maxDate)"
-  this.zoomCallback_ = attrs.zoomCallback || null;
+  // Make a note of whether labels will be pulled from the CSV file.
+  this.labelsFromCSV_ = (this.attr_("labels") == null);
 
   // Create the containing DIV and other interactive elements
   this.createInterface_();
 
   // Create the PlotKit grapher
 
   // Create the containing DIV and other interactive elements
   this.createInterface_();
 
   // Create the PlotKit grapher
-  this.layoutOptions_ = { 'errorBars': (this.errorBars_ || this.customBars_),
+  // TODO(danvk): why does the Layout need its own set of options?
+  this.layoutOptions_ = { 'errorBars': (this.attr_("errorBars") ||
+                                        this.customBars_),
                           'xOriginIsZero': false };
                           'xOriginIsZero': false };
-  MochiKit.Base.update(this.layoutOptions_, attrs);
-  this.setColors_(attrs);
+  MochiKit.Base.update(this.layoutOptions_, this.attrs_);
+  MochiKit.Base.update(this.layoutOptions_, this.user_attrs_);
 
 
-  this.layout_ = new DateGraphLayout(this.layoutOptions_);
+  this.layout_ = new DygraphLayout(this.layoutOptions_);
 
 
+  // TODO(danvk): why does the Renderer need its own set of options?
   this.renderOptions_ = { colorScheme: this.colors_,
                           strokeColor: null,
   this.renderOptions_ = { colorScheme: this.colors_,
                           strokeColor: null,
-                          strokeWidth: this.strokeWidth_,
+                          strokeWidth: this.attr_("strokeWidth"),
                           axisLabelFontSize: 14,
                           axisLabelFontSize: 14,
-                          axisLineWidth: DateGraph.AXIS_LINE_WIDTH };
-  MochiKit.Base.update(this.renderOptions_, attrs);
-  this.plotter_ = new DateGraphCanvasRenderer(this.hidden_, this.layout_,
-                                              this.renderOptions_);
+                          axisLineWidth: Dygraph.AXIS_LINE_WIDTH };
+  MochiKit.Base.update(this.renderOptions_, this.attrs_);
+  MochiKit.Base.update(this.renderOptions_, this.user_attrs_);
+  this.plotter_ = new DygraphCanvasRenderer(this.hidden_, this.layout_,
+                                            this.renderOptions_);
 
   this.createStatusMessage_();
   this.createRollInterface_();
 
   this.createStatusMessage_();
   this.createRollInterface_();
@@ -168,21 +211,60 @@ DateGraph.prototype.__init__ = function(div, file, labels, attrs) {
   this.start_();
 };
 
   this.start_();
 };
 
+Dygraph.prototype.attr_ = function(name) {
+  if (typeof(this.user_attrs_[name]) != 'undefined') {
+    return this.user_attrs_[name];
+  } else if (typeof(this.attrs_[name]) != 'undefined') {
+    return this.attrs_[name];
+  } else {
+    return null;
+  }
+};
+
+// TODO(danvk): any way I can get the line numbers to be this.warn call?
+Dygraph.prototype.log = function(severity, message) {
+  if (typeof(console) != 'undefined') {
+    switch (severity) {
+      case Dygraph.DEBUG:
+        console.debug('dygraphs: ' + message);
+        break;
+      case Dygraph.INFO:
+        console.info('dygraphs: ' + message);
+        break;
+      case Dygraph.WARNING:
+        console.warn('dygraphs: ' + message);
+        break;
+      case Dygraph.ERROR:
+        console.error('dygraphs: ' + message);
+        break;
+    }
+  }
+}
+Dygraph.prototype.info = function(message) {
+  this.log(Dygraph.INFO, message);
+}
+Dygraph.prototype.warn = function(message) {
+  this.log(Dygraph.WARNING, message);
+}
+Dygraph.prototype.error = function(message) {
+  this.log(Dygraph.ERROR, message);
+}
+
 /**
  * Returns the current rolling period, as set by the user or an option.
  * @return {Number} The number of days in the rolling window
  */
 /**
  * Returns the current rolling period, as set by the user or an option.
  * @return {Number} The number of days in the rolling window
  */
-DateGraph.prototype.rollPeriod = function() {
+Dygraph.prototype.rollPeriod = function() {
   return this.rollPeriod_;
 }
 
 /**
   return this.rollPeriod_;
 }
 
 /**
- * Generates interface elements for the DateGraph: a containing div, a div to
+ * Generates interface elements for the Dygraph: a containing div, a div to
  * display the current point, and a textbox to adjust the rolling average
  * period.
  * @private
  */
  * display the current point, and a textbox to adjust the rolling average
  * period.
  * @private
  */
-DateGraph.prototype.createInterface_ = function() {
+Dygraph.prototype.createInterface_ = function() {
   // Create the all-enclosing graph div
   var enclosing = this.maindiv_;
 
   // Create the all-enclosing graph div
   var enclosing = this.maindiv_;
 
@@ -205,12 +287,12 @@ DateGraph.prototype.createInterface_ = function() {
 
 /**
  * Creates the canvas containing the PlotKit graph. Only plotkit ever draws on
 
 /**
  * Creates the canvas containing the PlotKit graph. Only plotkit ever draws on
- * this particular canvas. All DateGraph work is done on this.canvas_.
- * @param {Object} canvas The DateGraph canvas to over which to overlay the plot
+ * this particular canvas. All Dygraph work is done on this.canvas_.
+ * @param {Object} canvas The Dygraph canvas to over which to overlay the plot
  * @return {Object} The newly-created canvas
  * @private
  */
  * @return {Object} The newly-created canvas
  * @private
  */
-DateGraph.prototype.createPlotKitCanvas_ = function(canvas) {
+Dygraph.prototype.createPlotKitCanvas_ = function(canvas) {
   var h = document.createElement("canvas");
   h.style.position = "absolute";
   h.style.top = canvas.style.top;
   var h = document.createElement("canvas");
   h.style.position = "absolute";
   h.style.top = canvas.style.top;
@@ -226,25 +308,33 @@ DateGraph.prototype.createPlotKitCanvas_ = function(canvas) {
  * color wheel. Saturation/Value are customizable, and the hue is
  * equally-spaced around the color wheel. If a custom set of colors is
  * specified, that is used instead.
  * color wheel. Saturation/Value are customizable, and the hue is
  * equally-spaced around the color wheel. If a custom set of colors is
  * specified, that is used instead.
- * @param {Object} attrs Various attributes, e.g. saturation and value
  * @private
  */
  * @private
  */
-DateGraph.prototype.setColors_ = function(attrs) {
-  var num = this.labels_.length;
+Dygraph.prototype.setColors_ = function() {
+  // TODO(danvk): compute this directly into this.attrs_['colorScheme'] and do
+  // away with this.renderOptions_.
+  var num = this.attr_("labels").length - 1;
   this.colors_ = [];
   this.colors_ = [];
-  if (!attrs.colors) {
-    var sat = attrs.colorSaturation || 1.0;
-    var val = attrs.colorValue || 0.5;
+  var colors = this.attr_('colors');
+  if (!colors) {
+    var sat = this.attr_('colorSaturation') || 1.0;
+    var val = this.attr_('colorValue') || 0.5;
     for (var i = 1; i <= num; i++) {
       var hue = (1.0*i/(1+num));
       this.colors_.push( MochiKit.Color.Color.fromHSV(hue, sat, val) );
     }
   } else {
     for (var i = 0; i < num; i++) {
     for (var i = 1; i <= num; i++) {
       var hue = (1.0*i/(1+num));
       this.colors_.push( MochiKit.Color.Color.fromHSV(hue, sat, val) );
     }
   } else {
     for (var i = 0; i < num; i++) {
-      var colorStr = attrs.colors[i % attrs.colors.length];
+      var colorStr = colors[i % colors.length];
       this.colors_.push( MochiKit.Color.Color.fromString(colorStr) );
     }
   }
       this.colors_.push( MochiKit.Color.Color.fromString(colorStr) );
     }
   }
+
+  // TODO(danvk): update this w/r/t/ the new options system. 
+  this.renderOptions_.colorScheme = this.colors_;
+  MochiKit.Base.update(this.plotter_.options, this.renderOptions_);
+  MochiKit.Base.update(this.layoutOptions_, this.user_attrs_);
+  MochiKit.Base.update(this.layoutOptions_, this.attrs_);
 }
 
 /**
 }
 
 /**
@@ -253,9 +343,9 @@ DateGraph.prototype.setColors_ = function(attrs) {
  * been specified.
  * @private
  */
  * been specified.
  * @private
  */
-DateGraph.prototype.createStatusMessage_ = function(){
-  if (!this.labelsDiv_) {
-    var divWidth = this.attrs_.labelsDivWidth;
+Dygraph.prototype.createStatusMessage_ = function(){
+  if (!this.attr_("labelsDiv")) {
+    var divWidth = this.attr_('labelsDivWidth');
     var messagestyle = { "style": {
       "position": "absolute",
       "fontSize": "14px",
     var messagestyle = { "style": {
       "position": "absolute",
       "fontSize": "14px",
@@ -266,9 +356,10 @@ DateGraph.prototype.createStatusMessage_ = function(){
       "background": "white",
       "textAlign": "left",
       "overflow": "hidden"}};
       "background": "white",
       "textAlign": "left",
       "overflow": "hidden"}};
-    MochiKit.Base.update(messagestyle["style"], this.attrs_.labelsDivStyles);
-    this.labelsDiv_ = MochiKit.DOM.DIV(messagestyle);
-    MochiKit.DOM.appendChildNodes(this.graphDiv, this.labelsDiv_);
+    MochiKit.Base.update(messagestyle["style"], this.attr_('labelsDivStyles'));
+    var div = MochiKit.DOM.DIV(messagestyle);
+    MochiKit.DOM.appendChildNodes(this.graphDiv, div);
+    this.attrs_.labelsDiv = div;
   }
 };
 
   }
 };
 
@@ -277,12 +368,9 @@ DateGraph.prototype.createStatusMessage_ = function(){
  * @return {Object} The newly-created text box
  * @private
  */
  * @return {Object} The newly-created text box
  * @private
  */
-DateGraph.prototype.createRollInterface_ = function() {
+Dygraph.prototype.createRollInterface_ = function() {
   var padding = this.plotter_.options.padding;
   var padding = this.plotter_.options.padding;
-  if (typeof this.attrs_.showRoller == 'undefined') {
-    this.attrs_.showRoller = false;
-  }
-  var display = this.attrs_.showRoller ? "block" : "none";
+  var display = this.attr_('showRoller') ? "block" : "none";
   var textAttr = { "type": "text",
                    "size": "2",
                    "value": this.rollPeriod_,
   var textAttr = { "type": "text",
                    "size": "2",
                    "value": this.rollPeriod_,
@@ -305,7 +393,7 @@ DateGraph.prototype.createRollInterface_ = function() {
  * events. Uses MochiKit.Signal to attach all the event handlers.
  * @private
  */
  * events. Uses MochiKit.Signal to attach all the event handlers.
  * @private
  */
-DateGraph.prototype.createDragInterface_ = function() {
+Dygraph.prototype.createDragInterface_ = function() {
   var self = this;
 
   // Tracks whether the mouse is down right now
   var self = this;
 
   // Tracks whether the mouse is down right now
@@ -371,9 +459,10 @@ DateGraph.prototype.createDragInterface_ = function() {
       var regionHeight = Math.abs(dragEndY - dragStartY);
 
       if (regionWidth < 2 && regionHeight < 2 &&
       var regionHeight = Math.abs(dragEndY - dragStartY);
 
       if (regionWidth < 2 && regionHeight < 2 &&
-          self.clickCallback_ != null &&
+          self.attr_('clickCallback') != null &&
           self.lastx_ != undefined) {
           self.lastx_ != undefined) {
-        self.clickCallback_(event, new Date(self.lastx_));
+        // TODO(danvk): pass along more info about the point.
+        self.attr_('clickCallback')(event, new Date(self.lastx_));
       }
 
       if (regionWidth >= 10) {
       }
 
       if (regionWidth >= 10) {
@@ -396,8 +485,8 @@ DateGraph.prototype.createDragInterface_ = function() {
     self.drawGraph_(self.rawData_);
     var minDate = self.rawData_[0][0];
     var maxDate = self.rawData_[self.rawData_.length - 1][0];
     self.drawGraph_(self.rawData_);
     var minDate = self.rawData_[0][0];
     var maxDate = self.rawData_[self.rawData_.length - 1][0];
-    if (self.zoomCallback_) {
-      self.zoomCallback_(minDate, maxDate);
+    if (self.attr_("zoomCallback")) {
+      self.attr_("zoomCallback")(minDate, maxDate);
     }
   });
 };
     }
   });
 };
@@ -414,7 +503,7 @@ DateGraph.prototype.createDragInterface_ = function() {
  * function. Used to avoid excess redrawing
  * @private
  */
  * function. Used to avoid excess redrawing
  * @private
  */
-DateGraph.prototype.drawZoomRect_ = function(startX, endX, prevEndX) {
+Dygraph.prototype.drawZoomRect_ = function(startX, endX, prevEndX) {
   var ctx = this.canvas_.getContext("2d");
 
   // Clean up from the previous rect if necessary
   var ctx = this.canvas_.getContext("2d");
 
   // Clean up from the previous rect if necessary
@@ -439,7 +528,7 @@ DateGraph.prototype.drawZoomRect_ = function(startX, endX, prevEndX) {
  * @param {Number} highX The rightmost pixel value that should be visible.
  * @private
  */
  * @param {Number} highX The rightmost pixel value that should be visible.
  * @private
  */
-DateGraph.prototype.doZoom_ = function(lowX, highX) {
+Dygraph.prototype.doZoom_ = function(lowX, highX) {
   // Find the earliest and latest dates contained in this canvasx range.
   var points = this.layout_.points;
   var minDate = null;
   // Find the earliest and latest dates contained in this canvasx range.
   var points = this.layout_.points;
   var minDate = null;
@@ -457,8 +546,8 @@ DateGraph.prototype.doZoom_ = function(lowX, highX) {
 
   this.dateWindow_ = [minDate, maxDate];
   this.drawGraph_(this.rawData_);
 
   this.dateWindow_ = [minDate, maxDate];
   this.drawGraph_(this.rawData_);
-  if (this.zoomCallback_) {
-    this.zoomCallback_(minDate, maxDate);
+  if (this.attr_("zoomCallback")) {
+    this.attr_("zoomCallback")(minDate, maxDate);
   }
 };
 
   }
 };
 
@@ -469,7 +558,7 @@ DateGraph.prototype.doZoom_ = function(lowX, highX) {
  * @param {Object} event The mousemove event from the browser.
  * @private
  */
  * @param {Object} event The mousemove event from the browser.
  * @private
  */
-DateGraph.prototype.mouseMove_ = function(event) {
+Dygraph.prototype.mouseMove_ = function(event) {
   var canvasx = event.mouse().page.x - PlotKit.Base.findPosX(this.hidden_);
   var points = this.layout_.points;
 
   var canvasx = event.mouse().page.x - PlotKit.Base.findPosX(this.hidden_);
   var points = this.layout_.points;
 
@@ -500,7 +589,7 @@ DateGraph.prototype.mouseMove_ = function(event) {
   }
 
   // Clear the previously drawn vertical, if there is one
   }
 
   // Clear the previously drawn vertical, if there is one
-  var circleSize = this.attrs_.highlightCircleSize;
+  var circleSize = this.attr_('highlightCircleSize');
   var ctx = this.canvas_.getContext("2d");
   if (this.previousVerticalX_ >= 0) {
     var px = this.previousVerticalX_;
   var ctx = this.canvas_.getContext("2d");
   if (this.previousVerticalX_ >= 0) {
     var px = this.previousVerticalX_;
@@ -511,10 +600,10 @@ DateGraph.prototype.mouseMove_ = function(event) {
     var canvasx = selPoints[0].canvasx;
 
     // Set the status message to indicate the selected point(s)
     var canvasx = selPoints[0].canvasx;
 
     // Set the status message to indicate the selected point(s)
-    var replace = this.xValueFormatter_(lastx) + ":";
+    var replace = this.attr_('xValueFormatter')(lastx, this) + ":";
     var clen = this.colors_.length;
     for (var i = 0; i < selPoints.length; i++) {
     var clen = this.colors_.length;
     for (var i = 0; i < selPoints.length; i++) {
-      if (this.labelsSeparateLines) {
+      if (this.attr_("labelsSeparateLines")) {
         replace += "<br/>";
       }
       var point = selPoints[i];
         replace += "<br/>";
       }
       var point = selPoints[i];
@@ -522,7 +611,7 @@ DateGraph.prototype.mouseMove_ = function(event) {
               + point.name + "</font></b>:"
               + this.round_(point.yval, 2);
     }
               + point.name + "</font></b>:"
               + this.round_(point.yval, 2);
     }
-    this.labelsDiv_.innerHTML = replace;
+    this.attr_("labelsDiv").innerHTML = replace;
 
     // Save last x position for callbacks.
     this.lastx_ = lastx;
 
     // Save last x position for callbacks.
     this.lastx_ = lastx;
@@ -546,14 +635,14 @@ DateGraph.prototype.mouseMove_ = function(event) {
  * @param {Object} event the mouseout event from the browser.
  * @private
  */
  * @param {Object} event the mouseout event from the browser.
  * @private
  */
-DateGraph.prototype.mouseOut_ = function(event) {
+Dygraph.prototype.mouseOut_ = function(event) {
   // Get rid of the overlay data
   var ctx = this.canvas_.getContext("2d");
   ctx.clearRect(0, 0, this.width_, this.height_);
   // Get rid of the overlay data
   var ctx = this.canvas_.getContext("2d");
   ctx.clearRect(0, 0, this.width_, this.height_);
-  this.labelsDiv_.innerHTML = "";
+  this.attr_("labelsDiv").innerHTML = "";
 };
 
 };
 
-DateGraph.zeropad = function(x) {
+Dygraph.zeropad = function(x) {
   if (x < 10) return "0" + x; else return "" + x;
 }
 
   if (x < 10) return "0" + x; else return "" + x;
 }
 
@@ -563,8 +652,8 @@ DateGraph.zeropad = function(x) {
  * @return {String} A time of the form "HH:MM:SS"
  * @private
  */
  * @return {String} A time of the form "HH:MM:SS"
  * @private
  */
-DateGraph.prototype.hmsString_ = function(date) {
-  var zeropad = DateGraph.zeropad;
+Dygraph.prototype.hmsString_ = function(date) {
+  var zeropad = Dygraph.zeropad;
   var d = new Date(date);
   if (d.getSeconds()) {
     return zeropad(d.getHours()) + ":" +
   var d = new Date(date);
   if (d.getSeconds()) {
     return zeropad(d.getHours()) + ":" +
@@ -582,9 +671,10 @@ DateGraph.prototype.hmsString_ = function(date) {
  * @param {Number} date The JavaScript date (ms since epoch)
  * @return {String} A date of the form "YYYY/MM/DD"
  * @private
  * @param {Number} date The JavaScript date (ms since epoch)
  * @return {String} A date of the form "YYYY/MM/DD"
  * @private
+ * TODO(danvk): why is this part of the prototype?
  */
  */
-DateGraph.prototype.dateString_ = function(date) {
-  var zeropad = DateGraph.zeropad;
+Dygraph.dateString_ = function(date, self) {
+  var zeropad = Dygraph.zeropad;
   var d = new Date(date);
 
   // Get the year:
   var d = new Date(date);
 
   // Get the year:
@@ -596,7 +686,7 @@ DateGraph.prototype.dateString_ = function(date) {
 
   var ret = "";
   var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
 
   var ret = "";
   var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
-  if (frac) ret = " " + this.hmsString_(date);
+  if (frac) ret = " " + self.hmsString_(date);
 
   return year + "/" + month + "/" + day + ret;
 };
 
   return year + "/" + month + "/" + day + ret;
 };
@@ -608,7 +698,7 @@ DateGraph.prototype.dateString_ = function(date) {
  * @return {Number} The rounded number
  * @private
  */
  * @return {Number} The rounded number
  * @private
  */
-DateGraph.prototype.round_ = function(num, places) {
+Dygraph.prototype.round_ = function(num, places) {
   var shift = Math.pow(10, places);
   return Math.round(num * shift)/shift;
 };
   var shift = Math.pow(10, places);
   return Math.round(num * shift)/shift;
 };
@@ -618,20 +708,20 @@ DateGraph.prototype.round_ = function(num, places) {
  * @param {String} data Raw CSV data to be plotted
  * @private
  */
  * @param {String} data Raw CSV data to be plotted
  * @private
  */
-DateGraph.prototype.loadedEvent_ = function(data) {
+Dygraph.prototype.loadedEvent_ = function(data) {
   this.rawData_ = this.parseCSV_(data);
   this.drawGraph_(this.rawData_);
 };
 
   this.rawData_ = this.parseCSV_(data);
   this.drawGraph_(this.rawData_);
 };
 
-DateGraph.prototype.months =  ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
+Dygraph.prototype.months =  ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
                                "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
-DateGraph.prototype.quarters = ["Jan", "Apr", "Jul", "Oct"];
+Dygraph.prototype.quarters = ["Jan", "Apr", "Jul", "Oct"];
 
 /**
  * Add ticks on the x-axis representing years, months, quarters, weeks, or days
  * @private
  */
 
 /**
  * Add ticks on the x-axis representing years, months, quarters, weeks, or days
  * @private
  */
-DateGraph.prototype.addXTicks_ = function() {
+Dygraph.prototype.addXTicks_ = function() {
   // Determine the correct ticks scale on the x-axis: quarterly, monthly, ...
   var startDate, endDate;
   if (this.dateWindow_) {
   // Determine the correct ticks scale on the x-axis: quarterly, monthly, ...
   var startDate, endDate;
   if (this.dateWindow_) {
@@ -642,57 +732,57 @@ DateGraph.prototype.addXTicks_ = function() {
     endDate   = this.rawData_[this.rawData_.length - 1][0];
   }
 
     endDate   = this.rawData_[this.rawData_.length - 1][0];
   }
 
-  var xTicks = this.xTicker_(startDate, endDate);
+  var xTicks = this.attr_('xTicker')(startDate, endDate, this);
   this.layout_.updateOptions({xTicks: xTicks});
 };
 
 // Time granularity enumeration
   this.layout_.updateOptions({xTicks: xTicks});
 };
 
 // Time granularity enumeration
-DateGraph.SECONDLY = 0;
-DateGraph.TEN_SECONDLY = 1;
-DateGraph.THIRTY_SECONDLY  = 2;
-DateGraph.MINUTELY = 3;
-DateGraph.TEN_MINUTELY = 4;
-DateGraph.THIRTY_MINUTELY = 5;
-DateGraph.HOURLY = 6;
-DateGraph.SIX_HOURLY = 7;
-DateGraph.DAILY = 8;
-DateGraph.WEEKLY = 9;
-DateGraph.MONTHLY = 10;
-DateGraph.QUARTERLY = 11;
-DateGraph.BIANNUAL = 12;
-DateGraph.ANNUAL = 13;
-DateGraph.DECADAL = 14;
-DateGraph.NUM_GRANULARITIES = 15;
-
-DateGraph.SHORT_SPACINGS = [];
-DateGraph.SHORT_SPACINGS[DateGraph.SECONDLY]        = 1000 * 1;
-DateGraph.SHORT_SPACINGS[DateGraph.TEN_SECONDLY]    = 1000 * 10;
-DateGraph.SHORT_SPACINGS[DateGraph.THIRTY_SECONDLY] = 1000 * 30;
-DateGraph.SHORT_SPACINGS[DateGraph.MINUTELY]        = 1000 * 60;
-DateGraph.SHORT_SPACINGS[DateGraph.TEN_MINUTELY]    = 1000 * 60 * 10;
-DateGraph.SHORT_SPACINGS[DateGraph.THIRTY_MINUTELY] = 1000 * 60 * 30;
-DateGraph.SHORT_SPACINGS[DateGraph.HOURLY]          = 1000 * 3600;
-DateGraph.SHORT_SPACINGS[DateGraph.HOURLY]          = 1000 * 3600 * 6;
-DateGraph.SHORT_SPACINGS[DateGraph.DAILY]           = 1000 * 86400;
-DateGraph.SHORT_SPACINGS[DateGraph.WEEKLY]          = 1000 * 604800;
+Dygraph.SECONDLY = 0;
+Dygraph.TEN_SECONDLY = 1;
+Dygraph.THIRTY_SECONDLY  = 2;
+Dygraph.MINUTELY = 3;
+Dygraph.TEN_MINUTELY = 4;
+Dygraph.THIRTY_MINUTELY = 5;
+Dygraph.HOURLY = 6;
+Dygraph.SIX_HOURLY = 7;
+Dygraph.DAILY = 8;
+Dygraph.WEEKLY = 9;
+Dygraph.MONTHLY = 10;
+Dygraph.QUARTERLY = 11;
+Dygraph.BIANNUAL = 12;
+Dygraph.ANNUAL = 13;
+Dygraph.DECADAL = 14;
+Dygraph.NUM_GRANULARITIES = 15;
+
+Dygraph.SHORT_SPACINGS = [];
+Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]        = 1000 * 1;
+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.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.HOURLY]          = 1000 * 3600 * 6;
+Dygraph.SHORT_SPACINGS[Dygraph.DAILY]           = 1000 * 86400;
+Dygraph.SHORT_SPACINGS[Dygraph.WEEKLY]          = 1000 * 604800;
 
 // NumXTicks()
 //
 //   If we used this time granularity, how many ticks would there be?
 //   This is only an approximation, but it's generally good enough.
 //
 
 // NumXTicks()
 //
 //   If we used this time granularity, how many ticks would there be?
 //   This is only an approximation, but it's generally good enough.
 //
-DateGraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
-  if (granularity < DateGraph.MONTHLY) {
+Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
+  if (granularity < Dygraph.MONTHLY) {
     // Generate one tick mark for every fixed interval of time.
     // Generate one tick mark for every fixed interval of time.
-    var spacing = DateGraph.SHORT_SPACINGS[granularity];
+    var spacing = Dygraph.SHORT_SPACINGS[granularity];
     return Math.floor(0.5 + 1.0 * (end_time - start_time) / spacing);
   } else {
     var year_mod = 1;  // e.g. to only print one point every 10 years.
     var num_months = 12;
     return Math.floor(0.5 + 1.0 * (end_time - start_time) / spacing);
   } else {
     var year_mod = 1;  // e.g. to only print one point every 10 years.
     var num_months = 12;
-    if (granularity == DateGraph.QUARTERLY) num_months = 3;
-    if (granularity == DateGraph.BIANNUAL) num_months = 2;
-    if (granularity == DateGraph.ANNUAL) num_months = 1;
-    if (granularity == DateGraph.DECADAL) { num_months = 1; year_mod = 10; }
+    if (granularity == Dygraph.QUARTERLY) num_months = 3;
+    if (granularity == Dygraph.BIANNUAL) num_months = 2;
+    if (granularity == Dygraph.ANNUAL) num_months = 1;
+    if (granularity == Dygraph.DECADAL) { num_months = 1; year_mod = 10; }
 
     var msInYear = 365.2524 * 24 * 3600 * 1000;
     var num_years = 1.0 * (end_time - start_time) / msInYear;
 
     var msInYear = 365.2524 * 24 * 3600 * 1000;
     var num_years = 1.0 * (end_time - start_time) / msInYear;
@@ -707,20 +797,20 @@ DateGraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
 //
 //   Returns an array containing {v: millis, label: label} dictionaries.
 //
 //
 //   Returns an array containing {v: millis, label: label} dictionaries.
 //
-DateGraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
+Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
   var ticks = [];
   var ticks = [];
-  if (granularity < DateGraph.MONTHLY) {
+  if (granularity < Dygraph.MONTHLY) {
     // Generate one tick mark for every fixed interval of time.
     // Generate one tick mark for every fixed interval of time.
-    var spacing = DateGraph.SHORT_SPACINGS[granularity];
+    var spacing = Dygraph.SHORT_SPACINGS[granularity];
     var format = '%d%b';  // e.g. "1 Jan"
     // TODO(danvk): be smarter about making sure this really hits a "nice" time.
     var format = '%d%b';  // e.g. "1 Jan"
     // TODO(danvk): be smarter about making sure this really hits a "nice" time.
-    if (granularity < DateGraph.HOURLY) {
+    if (granularity < Dygraph.HOURLY) {
       start_time = spacing * Math.floor(0.5 + start_time / spacing);
     }
     for (var t = start_time; t <= end_time; t += spacing) {
       var d = new Date(t);
       var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
       start_time = spacing * Math.floor(0.5 + start_time / spacing);
     }
     for (var t = start_time; t <= end_time; t += spacing) {
       var d = new Date(t);
       var frac = d.getHours() * 3600 + d.getMinutes() * 60 + d.getSeconds();
-      if (frac == 0 || granularity >= DateGraph.DAILY) {
+      if (frac == 0 || granularity >= Dygraph.DAILY) {
         // the extra hour covers DST problems.
         ticks.push({ v:t, label: new Date(t + 3600*1000).strftime(format) });
       } else {
         // the extra hour covers DST problems.
         ticks.push({ v:t, label: new Date(t + 3600*1000).strftime(format) });
       } else {
@@ -734,22 +824,22 @@ DateGraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
     var months;
     var year_mod = 1;  // e.g. to only print one point every 10 years.
 
     var months;
     var year_mod = 1;  // e.g. to only print one point every 10 years.
 
-    if (granularity == DateGraph.MONTHLY) {
+    if (granularity == Dygraph.MONTHLY) {
       months = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
       months = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ];
-    } else if (granularity == DateGraph.QUARTERLY) {
+    } else if (granularity == Dygraph.QUARTERLY) {
       months = [ 0, 3, 6, 9 ];
       months = [ 0, 3, 6, 9 ];
-    } else if (granularity == DateGraph.BIANNUAL) {
+    } else if (granularity == Dygraph.BIANNUAL) {
       months = [ 0, 6 ];
       months = [ 0, 6 ];
-    } else if (granularity == DateGraph.ANNUAL) {
+    } else if (granularity == Dygraph.ANNUAL) {
       months = [ 0 ];
       months = [ 0 ];
-    } else if (granularity == DateGraph.DECADAL) {
+    } else if (granularity == Dygraph.DECADAL) {
       months = [ 0 ];
       year_mod = 10;
     }
 
     var start_year = new Date(start_time).getFullYear();
     var end_year   = new Date(end_time).getFullYear();
       months = [ 0 ];
       year_mod = 10;
     }
 
     var start_year = new Date(start_time).getFullYear();
     var end_year   = new Date(end_time).getFullYear();
-    var zeropad = DateGraph.zeropad;
+    var zeropad = Dygraph.zeropad;
     for (var i = start_year; i <= end_year; i++) {
       if (i % year_mod != 0) continue;
       for (var j = 0; j < months.length; j++) {
     for (var i = start_year; i <= end_year; i++) {
       if (i % year_mod != 0) continue;
       for (var j = 0; j < months.length; j++) {
@@ -772,18 +862,18 @@ DateGraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
  * @return {Array.<Object>} Array of {label, value} tuples.
  * @public
  */
  * @return {Array.<Object>} Array of {label, value} tuples.
  * @public
  */
-DateGraph.prototype.dateTicker = function(startDate, endDate) {
+Dygraph.dateTicker = function(startDate, endDate, self) {
   var chosen = -1;
   var chosen = -1;
-  for (var i = 0; i < DateGraph.NUM_GRANULARITIES; i++) {
-    var num_ticks = this.NumXTicks(startDate, endDate, i);
-    if (this.width_ / num_ticks >= this.attrs_.pixelsPerXLabel) {
+  for (var i = 0; i < Dygraph.NUM_GRANULARITIES; i++) {
+    var num_ticks = self.NumXTicks(startDate, endDate, i);
+    if (self.width_ / num_ticks >= self.attr_('pixelsPerXLabel')) {
       chosen = i;
       break;
     }
   }
 
   if (chosen >= 0) {
       chosen = i;
       break;
     }
   }
 
   if (chosen >= 0) {
-    return this.GetXAxis(startDate, endDate, chosen);
+    return self.GetXAxis(startDate, endDate, chosen);
   } else {
     // TODO(danvk): signal error.
   }
   } else {
     // TODO(danvk): signal error.
   }
@@ -796,13 +886,15 @@ DateGraph.prototype.dateTicker = function(startDate, endDate) {
  * @return {Array.<Object>} Array of {label, value} tuples.
  * @public
  */
  * @return {Array.<Object>} Array of {label, value} tuples.
  * @public
  */
-DateGraph.prototype.numericTicks = function(minV, maxV) {
+Dygraph.numericTicks = function(minV, maxV, self) {
   // Basic idea:
   // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
   // Calculate the resulting tick spacing (i.e. this.height_ / nTicks).
   // Basic idea:
   // Try labels every 1, 2, 5, 10, 20, 50, 100, etc.
   // Calculate the resulting tick spacing (i.e. this.height_ / nTicks).
-  // The first spacing greater than this.attrs_.pixelsPerYLabel is what we use.
+  // The first spacing greater than pixelsPerYLabel is what we use.
   var mults = [1, 2, 5];
   var scale, low_val, high_val, nTicks;
   var mults = [1, 2, 5];
   var scale, low_val, high_val, nTicks;
+  // TODO(danvk): make it possible to set this for x- and y-axes independently.
+  var pixelsPerTick = self.attr_('pixelsPerYLabel');
   for (var i = -10; i < 50; i++) {
     var base_scale = Math.pow(10, i);
     for (var j = 0; j < mults.length; j++) {
   for (var i = -10; i < 50; i++) {
     var base_scale = Math.pow(10, i);
     for (var j = 0; j < mults.length; j++) {
@@ -810,26 +902,26 @@ DateGraph.prototype.numericTicks = function(minV, maxV) {
       low_val = Math.floor(minV / scale) * scale;
       high_val = Math.ceil(maxV / scale) * scale;
       nTicks = (high_val - low_val) / scale;
       low_val = Math.floor(minV / scale) * scale;
       high_val = Math.ceil(maxV / scale) * scale;
       nTicks = (high_val - low_val) / scale;
-      var spacing = this.height_ / nTicks;
+      var spacing = self.height_ / nTicks;
       // wish I could break out of both loops at once...
       // wish I could break out of both loops at once...
-      if (spacing > this.attrs_.pixelsPerYLabel) break;
+      if (spacing > pixelsPerTick) break;
     }
     }
-    if (spacing > this.attrs_.pixelsPerYLabel) break;
+    if (spacing > pixelsPerTick) break;
   }
 
   // Construct labels for the ticks
   var ticks = [];
   for (var i = 0; i < nTicks; i++) {
     var tickV = low_val + i * scale;
   }
 
   // Construct labels for the ticks
   var ticks = [];
   for (var i = 0; i < nTicks; i++) {
     var tickV = low_val + i * scale;
-    var label = this.round_(tickV, 2);
-    if (this.labelsKMB_) {
+    var label = self.round_(tickV, 2);
+    if (self.attr_("labelsKMB")) {
       var k = 1000;
       if (tickV >= k*k*k) {
       var k = 1000;
       if (tickV >= k*k*k) {
-        label = this.round_(tickV/(k*k*k), 1) + "B";
+        label = self.round_(tickV/(k*k*k), 1) + "B";
       } else if (tickV >= k*k) {
       } else if (tickV >= k*k) {
-        label = this.round_(tickV/(k*k), 1) + "M";
+        label = self.round_(tickV/(k*k), 1) + "M";
       } else if (tickV >= k) {
       } else if (tickV >= k) {
-        label = this.round_(tickV/k, 1) + "K";
+        label = self.round_(tickV/k, 1) + "K";
       }
     }
     ticks.push( {label: label, v: tickV} );
       }
     }
     ticks.push( {label: label, v: tickV} );
@@ -843,9 +935,10 @@ DateGraph.prototype.numericTicks = function(minV, maxV) {
  * @param {Number} maxY The maximum Y value in the data set
  * @private
  */
  * @param {Number} maxY The maximum Y value in the data set
  * @private
  */
-DateGraph.prototype.addYTicks_ = function(minY, maxY) {
+Dygraph.prototype.addYTicks_ = function(minY, maxY) {
   // Set the number of ticks so that the labels are human-friendly.
   // Set the number of ticks so that the labels are human-friendly.
-  var ticks = this.numericTicks(minY, maxY);
+  // TODO(danvk): make this an attribute as well.
+  var ticks = Dygraph.numericTicks(minY, maxY, this);
   this.layout_.updateOptions( { yAxis: [minY, maxY],
                                 yTicks: ticks } );
 };
   this.layout_.updateOptions( { yAxis: [minY, maxY],
                                 yTicks: ticks } );
 };
@@ -858,9 +951,11 @@ DateGraph.prototype.addYTicks_ = function(minY, maxY) {
  * @param {Array.<Object>} data The data (see above)
  * @private
  */
  * @param {Array.<Object>} data The data (see above)
  * @private
  */
-DateGraph.prototype.drawGraph_ = function(data) {
+Dygraph.prototype.drawGraph_ = function(data) {
   var maxY = null;
   this.layout_.removeAllDatasets();
   var maxY = null;
   this.layout_.removeAllDatasets();
+  this.setColors_();
+
   // Loop over all fields in the dataset
   for (var i = 1; i < data[0].length; i++) {
     var series = [];
   // Loop over all fields in the dataset
   for (var i = 1; i < data[0].length; i++) {
     var series = [];
@@ -871,7 +966,7 @@ DateGraph.prototype.drawGraph_ = function(data) {
     series = this.rollingAverage(series, this.rollPeriod_);
 
     // Prune down to the desired range, if necessary (for zooming)
     series = this.rollingAverage(series, this.rollPeriod_);
 
     // Prune down to the desired range, if necessary (for zooming)
-    var bars = this.errorBars_ || this.customBars_;
+    var bars = this.attr_("errorBars") || this.customBars_;
     if (this.dateWindow_) {
       var low = this.dateWindow_[0];
       var high= this.dateWindow_[1];
     if (this.dateWindow_) {
       var low = this.dateWindow_[0];
       var high= this.dateWindow_[1];
@@ -885,10 +980,22 @@ DateGraph.prototype.drawGraph_ = function(data) {
       }
       series = pruned;
     } else {
       }
       series = pruned;
     } else {
-      for (var j = 0; j < series.length; j++) {
-        var y = bars ? series[j][1][0] : series[j][1];
-        if (maxY == null || y > maxY) {
-          maxY = bars ? y + series[j][1][1] : y;
+      if (!this.customBars_) {
+        for (var j = 0; j < series.length; j++) {
+          var y = bars ? series[j][1][0] : series[j][1];
+          if (maxY == null || y > maxY) {
+            maxY = bars ? y + series[j][1][1] : y;
+          }
+        }
+      } else {
+        // With custom bars, maxY is the max of the high values.
+        for (var j = 0; j < series.length; j++) {
+          var y = series[j][1][0];
+          var high = series[j][1][2];
+          if (high > y) y = high;
+          if (maxY == null || y > maxY) {
+            maxY = y;
+          }
         }
       }
     }
         }
       }
     }
@@ -898,9 +1005,9 @@ DateGraph.prototype.drawGraph_ = function(data) {
       for (var j=0; j<series.length; j++)
         vals[j] = [series[j][0],
                    series[j][1][0], series[j][1][1], series[j][1][2]];
       for (var j=0; j<series.length; j++)
         vals[j] = [series[j][0],
                    series[j][1][0], series[j][1][1], series[j][1][2]];
-      this.layout_.addDataset(this.labels_[i - 1], vals);
+      this.layout_.addDataset(this.attr_("labels")[i], vals);
     } else {
     } else {
-      this.layout_.addDataset(this.labels_[i - 1], series);
+      this.layout_.addDataset(this.attr_("labels")[i], series);
     }
   }
 
     }
   }
 
@@ -936,12 +1043,12 @@ DateGraph.prototype.drawGraph_ = function(data) {
  * @param {Array} originalData The data in the appropriate format (see above)
  * @param {Number} rollPeriod The number of days over which to average the data
  */
  * @param {Array} originalData The data in the appropriate format (see above)
  * @param {Number} rollPeriod The number of days over which to average the data
  */
-DateGraph.prototype.rollingAverage = function(originalData, rollPeriod) {
+Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
   if (originalData.length < 2)
     return originalData;
   var rollPeriod = Math.min(rollPeriod, originalData.length - 1);
   var rollingData = [];
   if (originalData.length < 2)
     return originalData;
   var rollPeriod = Math.min(rollPeriod, originalData.length - 1);
   var rollingData = [];
-  var sigma = this.sigma_;
+  var sigma = this.attr_("sigma");
 
   if (this.fractions_) {
     var num = 0;
 
   if (this.fractions_) {
     var num = 0;
@@ -957,7 +1064,7 @@ DateGraph.prototype.rollingAverage = function(originalData, rollPeriod) {
 
       var date = originalData[i][0];
       var value = den ? num / den : 0.0;
 
       var date = originalData[i][0];
       var value = den ? num / den : 0.0;
-      if (this.errorBars_) {
+      if (this.attr_("errorBars")) {
         if (this.wilsonInterval_) {
           // For more details on this confidence interval, see:
           // http://en.wikipedia.org/wiki/Binomial_confidence_interval
         if (this.wilsonInterval_) {
           // For more details on this confidence interval, see:
           // http://en.wikipedia.org/wiki/Binomial_confidence_interval
@@ -1009,7 +1116,7 @@ DateGraph.prototype.rollingAverage = function(originalData, rollPeriod) {
     // Calculate the rolling average for the first rollPeriod - 1 points where
     // there is not enough data to roll over the full number of days
     var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
     // Calculate the rolling average for the first rollPeriod - 1 points where
     // there is not enough data to roll over the full number of days
     var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
-    if (!this.errorBars_){
+    if (!this.attr_("errorBars")){
       for (var i = 0; i < num_init_points; i++) {
         var sum = 0;
         for (var j = 0; j < i + 1; j++)
       for (var i = 0; i < num_init_points; i++) {
         var sum = 0;
         for (var j = 0; j < i + 1; j++)
@@ -1059,27 +1166,63 @@ DateGraph.prototype.rollingAverage = function(originalData, rollPeriod) {
 
 /**
  * Parses a date, returning the number of milliseconds since epoch. This can be
 
 /**
  * Parses a date, returning the number of milliseconds since epoch. This can be
- * passed in as an xValueParser in the DateGraph constructor.
+ * passed in as an xValueParser in the Dygraph constructor.
+ * TODO(danvk): enumerate formats that this understands.
  * @param {String} A date in YYYYMMDD format.
  * @return {Number} Milliseconds since epoch.
  * @public
  */
  * @param {String} A date in YYYYMMDD format.
  * @return {Number} Milliseconds since epoch.
  * @public
  */
-DateGraph.prototype.dateParser = function(dateStr) {
+Dygraph.dateParser = function(dateStr, self) {
   var dateStrSlashed;
   var dateStrSlashed;
+  var d;
   if (dateStr.length == 10 && dateStr.search("-") != -1) {  // e.g. '2009-07-12'
     dateStrSlashed = dateStr.replace("-", "/", "g");
     while (dateStrSlashed.search("-") != -1) {
       dateStrSlashed = dateStrSlashed.replace("-", "/");
     }
   if (dateStr.length == 10 && dateStr.search("-") != -1) {  // e.g. '2009-07-12'
     dateStrSlashed = dateStr.replace("-", "/", "g");
     while (dateStrSlashed.search("-") != -1) {
       dateStrSlashed = dateStrSlashed.replace("-", "/");
     }
-    return Date.parse(dateStrSlashed);
+    d = Date.parse(dateStrSlashed);
   } else if (dateStr.length == 8) {  // e.g. '20090712'
   } else if (dateStr.length == 8) {  // e.g. '20090712'
+    // TODO(danvk): remove support for this format. It's confusing.
     dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2)
                        + "/" + dateStr.substr(6,2);
     dateStrSlashed = dateStr.substr(0,4) + "/" + dateStr.substr(4,2)
                        + "/" + dateStr.substr(6,2);
-    return Date.parse(dateStrSlashed);
+    d = Date.parse(dateStrSlashed);
   } else {
     // Any format that Date.parse will accept, e.g. "2009/07/12" or
     // "2009/07/12 12:34:56"
   } else {
     // Any format that Date.parse will accept, e.g. "2009/07/12" or
     // "2009/07/12 12:34:56"
-    return Date.parse(dateStr);
+    d = Date.parse(dateStr);
+  }
+
+  if (!d || isNaN(d)) {
+    self.error("Couldn't parse " + dateStr + " as a date");
+  }
+  return d;
+};
+
+/**
+ * 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.
+ * @private
+ */
+Dygraph.prototype.detectTypeFromString_ = function(str) {
+  var isDate = false;
+  if (str.indexOf('-') >= 0 ||
+      str.indexOf('/') >= 0 ||
+      isNaN(parseFloat(str))) {
+    isDate = true;
+  } else if (str.length == 8 && str > '19700101' && str < '20371231') {
+    // TODO(danvk): remove support for this format.
+    isDate = true;
+  }
+
+  if (isDate) {
+    this.attrs_.xValueFormatter = Dygraph.dateString_;
+    this.attrs_.xValueParser = Dygraph.dateParser;
+    this.attrs_.xTicker = Dygraph.dateTicker;
+  } else {
+    this.attrs_.xValueFormatter = function(x) { return x; };
+    this.attrs_.xValueParser = function(x) { return parseFloat(x); };
+    this.attrs_.xTicker = Dygraph.numericTicks;
   }
 };
 
   }
 };
 
@@ -1087,35 +1230,44 @@ DateGraph.prototype.dateParser = function(dateStr) {
  * Parses a string in a special csv format.  We expect a csv file where each
  * line is a date point, and the first field in each line is the date string.
  * We also expect that all remaining fields represent series.
  * Parses a string in a special csv format.  We expect a csv file where each
  * line is a date point, and the first field in each line is the date string.
  * We also expect that all remaining fields represent series.
- * if this.errorBars_ is set, then interpret the fields as:
+ * if the errorBars attribute is set, then interpret the fields as:
  * date, series1, stddev1, series2, stddev2, ...
  * @param {Array.<Object>} data See above.
  * @private
  * date, series1, stddev1, series2, stddev2, ...
  * @param {Array.<Object>} data See above.
  * @private
+ *
+ * @return Array.<Object> An array with one entry for each row. These entries
+ * are an array of cells in that row. The first entry is the parsed x-value for
+ * the row. The second, third, etc. are the y-values. These can take on one of
+ * three forms, depending on the CSV and constructor parameters:
+ * 1. numeric value
+ * 2. [ value, stddev ]
+ * 3. [ low value, center value, high value ]
  */
  */
-DateGraph.prototype.parseCSV_ = function(data) {
+Dygraph.prototype.parseCSV_ = function(data) {
   var ret = [];
   var lines = data.split("\n");
   var ret = [];
   var lines = data.split("\n");
-  var start = this.labelsFromCSV_ ? 1 : 0;
+  var start = 0;
   if (this.labelsFromCSV_) {
   if (this.labelsFromCSV_) {
-    var labels = lines[0].split(",");
-    labels.shift();  // a "date" parameter is assumed.
-    this.labels_ = labels;
-    // regenerate automatic colors.
-    this.setColors_(this.attrs_);
-    this.renderOptions_.colorScheme = this.colors_;
-    MochiKit.Base.update(this.plotter_.options, this.renderOptions_);
-    MochiKit.Base.update(this.layoutOptions_, this.attrs_);
+    start = 1;
+    this.attrs_.labels = lines[0].split(",");
   }
 
   }
 
+  var xParser;
+  var defaultParserSet = false;  // attempt to auto-detect x value type
+  var expectedCols = this.attr_("labels").length;
   for (var i = start; i < lines.length; i++) {
     var line = lines[i];
     if (line.length == 0) continue;  // skip blank lines
     var inFields = line.split(',');
   for (var i = start; i < lines.length; i++) {
     var line = lines[i];
     if (line.length == 0) continue;  // skip blank lines
     var inFields = line.split(',');
-    if (inFields.length < 2)
-      continue;
+    if (inFields.length < 2) continue;
 
     var fields = [];
 
     var fields = [];
-    fields[0] = this.xValueParser_(inFields[0]);
+    if (!defaultParserSet) {
+      this.detectTypeFromString_(inFields[0]);
+      xParser = this.attr_("xValueParser");
+      defaultParserSet = true;
+    }
+    fields[0] = xParser(inFields[0], this);
 
     // If fractions are expected, parse the numbers as "A/B"
     if (this.fractions_) {
 
     // If fractions are expected, parse the numbers as "A/B"
     if (this.fractions_) {
@@ -1124,7 +1276,7 @@ DateGraph.prototype.parseCSV_ = function(data) {
         var vals = inFields[j].split("/");
         fields[j] = [parseFloat(vals[0]), parseFloat(vals[1])];
       }
         var vals = inFields[j].split("/");
         fields[j] = [parseFloat(vals[0]), parseFloat(vals[1])];
       }
-    } else if (this.errorBars_) {
+    } else if (this.attr_("errorBars")) {
       // If there are error bars, values are (value, stddev) pairs
       for (var j = 1; j < inFields.length; j += 2)
         fields[(j + 1) / 2] = [parseFloat(inFields[j]),
       // If there are error bars, values are (value, stddev) pairs
       for (var j = 1; j < inFields.length; j += 2)
         fields[(j + 1) / 2] = [parseFloat(inFields[j]),
@@ -1139,15 +1291,77 @@ DateGraph.prototype.parseCSV_ = function(data) {
       }
     } else {
       // Values are just numbers
       }
     } else {
       // Values are just numbers
-      for (var j = 1; j < inFields.length; j++)
+      for (var j = 1; j < inFields.length; j++) {
         fields[j] = parseFloat(inFields[j]);
         fields[j] = parseFloat(inFields[j]);
+      }
     }
     ret.push(fields);
     }
     ret.push(fields);
+
+    if (fields.length != expectedCols) {
+      this.error("Number of columns in line " + i + " (" + fields.length +
+                 ") does not agree with number of labels (" + expectedCols +
+                 ") " + line);
+    }
   }
   return ret;
 };
 
 /**
   }
   return ret;
 };
 
 /**
+ * The user has provided their data as a pre-packaged JS array. If the x values
+ * are numeric, this is the same as dygraphs' internal format. If the x values
+ * are dates, we need to convert them from Date objects to ms since epoch.
+ * @param {Array.<Object>} data
+ * @return {Array.<Object>} data with numeric x values.
+ */
+Dygraph.prototype.parseArray_ = function(data) {
+  // Peek at the first x value to see if it's numeric.
+  if (data.length == 0) {
+    this.error("Can't plot empty data set");
+    return null;
+  }
+  if (data[0].length == 0) {
+    this.error("Data set cannot contain an empty row");
+    return null;
+  }
+
+  if (this.attr_("labels") == null) {
+    this.warn("Using default labels. Set labels explicitly via 'labels' " +
+              "in the options parameter");
+    this.attrs_.labels = [ "X" ];
+    for (var i = 1; i < data[0].length; i++) {
+      this.attrs_.labels.push("Y" + i);
+    }
+  }
+
+  if (MochiKit.Base.isDateLike(data[0][0])) {
+    // Some intelligent defaults for a date x-axis.
+    this.attrs_.xValueFormatter = Dygraph.dateString_;
+    this.attrs_.xTicker = Dygraph.dateTicker;
+
+    // Assume they're all dates.
+    var parsedData = MochiKit.Base.clone(data);
+    for (var i = 0; i < data.length; i++) {
+      if (parsedData[i].length == 0) {
+        this.error("Row " << (1 + i) << " of data is empty");
+        return null;
+      }
+      if (parsedData[i][0] == null
+          || typeof(parsedData[i][0].getTime) != 'function') {
+        this.error("x value in row " << (1 + i) << " is not a Date");
+        return null;
+      }
+      parsedData[i][0] = parsedData[i][0].getTime();
+    }
+    return parsedData;
+  } else {
+    // Some intelligent defaults for a numeric x-axis.
+    this.attrs_.xValueFormatter = function(x) { return x; };
+    this.attrs_.xTicker = Dygraph.numericTicks;
+    return data;
+  }
+};
+
+/**
  * Parses a DataTable object from gviz.
  * The data is expected to have a first column that is either a date or a
  * number. All subsequent columns must be numbers. If there is a clear mismatch
  * Parses a DataTable object from gviz.
  * The data is expected to have a first column that is either a date or a
  * number. All subsequent columns must be numbers. If there is a clear mismatch
@@ -1156,7 +1370,7 @@ DateGraph.prototype.parseCSV_ = function(data) {
  * @param {Array.<Object>} data See above.
  * @private
  */
  * @param {Array.<Object>} data See above.
  * @private
  */
-DateGraph.prototype.parseDataTable_ = function(data) {
+Dygraph.prototype.parseDataTable_ = function(data) {
   var cols = data.getNumberOfColumns();
   var rows = data.getNumberOfRows();
 
   var cols = data.getNumberOfColumns();
   var rows = data.getNumberOfRows();
 
@@ -1165,19 +1379,20 @@ DateGraph.prototype.parseDataTable_ = function(data) {
   for (var i = 0; i < cols; i++) {
     labels.push(data.getColumnLabel(i));
   }
   for (var i = 0; i < cols; i++) {
     labels.push(data.getColumnLabel(i));
   }
-  labels.shift();  // the x-axis parameter is assumed and unnamed.
-  this.labels_ = labels;
-  // regenerate automatic colors.
-  this.setColors_(this.attrs_);
-  this.renderOptions_.colorScheme = this.colors_;
-  MochiKit.Base.update(this.plotter_.options, this.renderOptions_);
-  MochiKit.Base.update(this.layoutOptions_, this.attrs_);
+  this.attrs_.labels = labels;
 
   var indepType = data.getColumnType(0);
 
   var indepType = data.getColumnType(0);
-  if (indepType != 'date' && indepType != 'number') {
-    // TODO(danvk): standardize error reporting.
-    alert("only 'date' and 'number' types are supported for column 1" +
-          "of DataTable input (Got '" + indepType + "')");
+  if (indepType == 'date') {
+    this.attrs_.xValueFormatter = Dygraph.dateString_;
+    this.attrs_.xValueParser = Dygraph.dateParser;
+    this.attrs_.xTicker = Dygraph.dateTicker;
+  } else if (indepType != 'number') {
+    this.attrs_.xValueFormatter = function(x) { return x; };
+    this.attrs_.xValueParser = function(x) { return parseFloat(x); };
+    this.attrs_.xTicker = Dygraph.numericTicks;
+  } else {
+    this.error("only 'date' and 'number' types are supported for column 1" +
+               "of DataTable input (Got '" + indepType + "')");
     return null;
   }
 
     return null;
   }
 
@@ -1202,28 +1417,38 @@ DateGraph.prototype.parseDataTable_ = function(data) {
  * file, do an XMLHttpRequest to get it.
  * @private
  */
  * file, do an XMLHttpRequest to get it.
  * @private
  */
-DateGraph.prototype.start_ = function() {
+Dygraph.prototype.start_ = function() {
   if (typeof this.file_ == 'function') {
   if (typeof this.file_ == 'function') {
-    // Stubbed out to allow this to run off a filesystem
+    // CSV string. Pretend we got it via XHR.
     this.loadedEvent_(this.file_());
     this.loadedEvent_(this.file_());
+  } else if (MochiKit.Base.isArrayLike(this.file_)) {
+    this.rawData_ = this.parseArray_(this.file_);
+    this.drawGraph_(this.rawData_);
   } else if (typeof this.file_ == 'object' &&
              typeof this.file_.getColumnRange == 'function') {
     // must be a DataTable from gviz.
     this.rawData_ = this.parseDataTable_(this.file_);
     this.drawGraph_(this.rawData_);
   } else if (typeof this.file_ == 'object' &&
              typeof this.file_.getColumnRange == 'function') {
     // must be a DataTable from gviz.
     this.rawData_ = this.parseDataTable_(this.file_);
     this.drawGraph_(this.rawData_);
-  } else {
-    var req = new XMLHttpRequest();
-    var caller = this;
-    req.onreadystatechange = function () {
-      if (req.readyState == 4) {
-        if (req.status == 200) {
-          caller.loadedEvent_(req.responseText);
+  } else if (typeof this.file_ == 'string') {
+    // Heuristic: a newline means it's CSV data. Otherwise it's an URL.
+    if (this.file_.indexOf('\n') >= 0) {
+      this.loadedEvent_(this.file_);
+    } else {
+      var req = new XMLHttpRequest();
+      var caller = this;
+      req.onreadystatechange = function () {
+        if (req.readyState == 4) {
+          if (req.status == 200) {
+            caller.loadedEvent_(req.responseText);
+          }
         }
         }
-      }
-    };
+      };
 
 
-    req.open("GET", this.file_, true);
-    req.send(null);
+      req.open("GET", this.file_, true);
+      req.send(null);
+    }
+  } else {
+    this.error("Unknown data format: " + (typeof this.file_));
   }
 };
 
   }
 };
 
@@ -1235,16 +1460,11 @@ DateGraph.prototype.start_ = function() {
  * </ul>
  * @param {Object} attrs The new properties and values
  */
  * </ul>
  * @param {Object} attrs The new properties and values
  */
-DateGraph.prototype.updateOptions = function(attrs) {
-  if (attrs.errorBars) {
-    this.errorBars_ = attrs.errorBars;
-  }
+Dygraph.prototype.updateOptions = function(attrs) {
+  // TODO(danvk): this is a mess. Rethink this function.
   if (attrs.customBars) {
     this.customBars_ = attrs.customBars;
   }
   if (attrs.customBars) {
     this.customBars_ = attrs.customBars;
   }
-  if (attrs.strokeWidth) {
-    this.strokeWidth_ = attrs.strokeWidth;
-  }
   if (attrs.rollPeriod) {
     this.rollPeriod_ = attrs.rollPeriod;
   }
   if (attrs.rollPeriod) {
     this.rollPeriod_ = attrs.rollPeriod;
   }
@@ -1254,12 +1474,12 @@ DateGraph.prototype.updateOptions = function(attrs) {
   if (attrs.valueRange) {
     this.valueRange_ = attrs.valueRange;
   }
   if (attrs.valueRange) {
     this.valueRange_ = attrs.valueRange;
   }
-  MochiKit.Base.update(this.attrs_, attrs);
-  if (typeof(attrs.labels) != 'undefined') {
-    this.labels_ = attrs.labels;
-    this.labelsFromCSV_ = (attrs.labels == null);
-  }
-  this.layout_.updateOptions({ 'errorBars': this.errorBars_ });
+  MochiKit.Base.update(this.user_attrs_, attrs);
+
+  this.labelsFromCSV_ = (this.attr_("labels") == null);
+
+  // TODO(danvk): this doesn't match the constructor logic
+  this.layout_.updateOptions({ 'errorBars': this.attr_("errorBars") });
   if (attrs['file'] && attrs['file'] != this.file_) {
     this.file_ = attrs['file'];
     this.start_();
   if (attrs['file'] && attrs['file'] != this.file_) {
     this.file_ = attrs['file'];
     this.start_();
@@ -1273,21 +1493,24 @@ DateGraph.prototype.updateOptions = function(attrs) {
  * reflect the new averaging period.
  * @param {Number} length Number of days over which to average the data.
  */
  * reflect the new averaging period.
  * @param {Number} length Number of days over which to average the data.
  */
-DateGraph.prototype.adjustRoll = function(length) {
+Dygraph.prototype.adjustRoll = function(length) {
   this.rollPeriod_ = length;
   this.drawGraph_(this.rawData_);
 };
 
 
 /**
   this.rollPeriod_ = length;
   this.drawGraph_(this.rawData_);
 };
 
 
 /**
- * A wrapper around DateGraph that implements the gviz API.
+ * A wrapper around Dygraph that implements the gviz API.
  * @param {Object} container The DOM object the visualization should live in.
  */
  * @param {Object} container The DOM object the visualization should live in.
  */
-DateGraph.GVizChart = function(container) {
+Dygraph.GVizChart = function(container) {
   this.container = container;
 }
 
   this.container = container;
 }
 
-DateGraph.GVizChart.prototype.draw = function(data, options) {
+Dygraph.GVizChart.prototype.draw = function(data, options) {
   this.container.innerHTML = '';
   this.container.innerHTML = '';
-  this.date_graph = new DateGraph(this.container, data, null, options || {});
+  this.date_graph = new Dygraph(this.container, data, options);
 }
 }
+
+// Older pages may still use this name.
+DateGraph = Dygraph;
index 8fba03d..7895cb9 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
     <style type="text/css">
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
     <style type="text/css">
@@ -17,8 +18,7 @@
     <p>Hopefully this stays in its border:</p>
     <div id="bordered" style="width:600px; height:300px;"></div>
     <script type="text/javascript">
     <p>Hopefully this stays in its border:</p>
     <div id="bordered" style="width:600px; height:300px;"></div>
     <script type="text/javascript">
-    new DateGraph(document.getElementById('bordered'),
-                  data, null, {});
+    new Dygraph(document.getElementById('bordered'), data);
     </script>
   </body>
 </html>
     </script>
   </body>
 </html>
diff --git a/tests/custom-bars.html b/tests/custom-bars.html
new file mode 100644 (file)
index 0000000..4663cc6
--- /dev/null
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <title>custom bars</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+    <script type="text/javascript" src="data.js"></script>
+  </head>
+  <body>
+    <p>Top and bottom of the bars stay mostly fixed while the middle varies.</p>
+    <div id="graph"></div>
+
+    <script type="text/javascript">
+      new Dygraph(document.getElementById("graph"),
+                  [
+                    [1, [10,  10, 100]],
+                    [2, [15,  20, 110]],
+                    [3, [10,  30, 100]],
+                    [4, [15,  40, 110]],
+                    [5, [10, 120, 100]],
+                    [6, [15,  50, 110]],
+                    [7, [10,  70, 100]],
+                    [8, [15,  90, 110]],
+                    [9, [10,  50, 100]]
+                  ], {
+                    customBars: true,
+                    errorBars: true
+                  }
+                 );
+    </script>
+  </body>
+</html>
index c6a1c92..63ec7e2 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <div id="g14" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="g14" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g14 = new DateGraph(
+      g14 = new Dygraph(
             document.getElementById("g14"),
             document.getElementById("g14"),
-            NoisyData, null, {
+            NoisyData, {
               rollPeriod: 14,
               errorBars: true,
               rollPeriod: 14,
               errorBars: true,
-              labelsDivWidth: 160,
+              labelsDivWidth: 100,
               labelsDivStyles: {
                 'background-color': 'transparent',
               labelsDivStyles: {
                 'background-color': 'transparent',
-                'top': '10px'
+                'top': '210px'
               },
               },
+              labelsSeparateLines: true,
               padding: {
                 left: 40,
                 top: 0,
               padding: {
                 left: 40,
                 top: 0,
index 75da280..9dbdc27 100644 (file)
@@ -1,6 +1,5 @@
-function data() {
+function data_nolabel() {
 return "" +
 return "" +
-"Date,High,Low\n" +
 "20070101,62,39\n" +
 "20070102,62,44\n" +
 "20070103,62,42\n" +
 "20070101,62,39\n" +
 "20070102,62,44\n" +
 "20070103,62,42\n" +
@@ -368,6 +367,10 @@ return "" +
 "20071231,57,42\n";
 }
 
 "20071231,57,42\n";
 }
 
+function data() {
+  return "Date,High,Low\n" + data_nolabel();
+}
+
 function NoisyData() {
 return "" +
 "Date,A,B\n" +
 function NoisyData() {
 return "" +
 "Date,A,B\n" +
diff --git a/tests/demo.html b/tests/demo.html
new file mode 100644 (file)
index 0000000..1fa66fd
--- /dev/null
@@ -0,0 +1,51 @@
+<html>
+  <head>
+    <title>noise</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+  </head>
+  <body>
+    <h2>Demo</h2>
+    <font size=-1>(Mouse over to highlight individual values. Click and drag to zoom. Double-click to zoom out.)</font><br/>
+    <table><tr><td>
+    <div id="demodiv" style="width:480px; height:320px;"></div>
+    </td><td valign=top>
+    <div id="status" style="width:200px; font-size:0.8em; padding-top:5px;"></div>
+    </td>
+    </tr></table>
+    <script type="text/javascript">
+      g = new DateGraph(
+              document.getElementById("demodiv"),
+              function() {
+                var zp = function(x) { if (x < 10) return "0"+x; else return x; };
+                var r = "date,parabola,line,another line,sine wave\n";
+                for (var i=1; i<=31; i++) {
+                r += "200610" + zp(i);
+                r += "," + 10*(i*(31-i));
+                r += "," + 10*(8*i);
+                r += "," + 10*(250 - 8*i);
+                r += "," + 10*(125 + 125 * Math.sin(0.3*i));
+                r += "\n";
+                }
+                return r;
+              },
+              null,
+              {
+                rollPeriod: 1,
+                labelsDiv: document.getElementById('status'),
+                labelsSeparateLines: true,
+                labelsKMB: true,
+                colors: ["hsl(180,60,50)",
+                         "rgb(255,100,100)",
+                         "#00DD55",
+                         "rgba(50,50,200,0.4)"],
+                padding: {left: 40, right: 30, top: 15, bottom: 15}
+              }
+          );
+    </script>
+</body>
+</html>
diff --git a/tests/dygraph.html b/tests/dygraph.html
new file mode 100644 (file)
index 0000000..9cb1237
--- /dev/null
@@ -0,0 +1,35 @@
+<html>
+  <head>
+    <title>noise</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+  </head>
+  <body>
+    <p>Minimal example of a dygraph chart:</p>
+    <div id="graphdiv"></div>
+    <script type="text/javascript">
+      g = new Dygraph(document.getElementById("graphdiv"),
+                      "Date,Temperature\n" +
+                      "2008-05-07,75\n" +
+                      "2008-05-08,70\n" +
+                      "2008-05-09,80\n");
+    </script>
+
+    <p>Same data, specified in a parsed format:</p>
+    <div id="graphdiv2"></div>
+    <script type="text/javascript">
+      g2 = new Dygraph(document.getElementById("graphdiv2"),
+                       [ [ new Date("2008/05/07"), 75],
+                         [ new Date("2008/05/08"), 70],
+                         [ new Date("2008/05/09"), 80]
+                       ],
+                       {
+                               labels: [ "Date", "Temperature" ]
+                       });
+    </script>
+  </body>
+</html>
index 789b7aa..8b92fdb 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
@@ -13,9 +14,9 @@
     <div id="g14" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="g14" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g14 = new DateGraph(
+      g14 = new Dygraph(
             document.getElementById("g14"),
             document.getElementById("g14"),
-            NoisyData, null, {
+            NoisyData, {
               rollPeriod: 14,
               errorBars: true,
               gridLineColor: MochiKit.Color.Color.redColor(),
               rollPeriod: 14,
               errorBars: true,
               gridLineColor: MochiKit.Color.Color.redColor(),
index 8cf8c45..885b0d5 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="http://www.google.com/jsapi"></script>
     <script type="text/javascript">
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="http://www.google.com/jsapi"></script>
     <script type="text/javascript">
@@ -32,7 +33,7 @@
         new google.visualization.LineChart(
             document.getElementById('gviz')).draw(data, null);  
 
         new google.visualization.LineChart(
             document.getElementById('gviz')).draw(data, null);  
 
-        new DateGraph.GVizChart(
+        new Dygraph.GVizChart(
             document.getElementById('dygraphs')).draw(data, null);
       }
       google.setOnLoadCallback(drawVisualization);
             document.getElementById('dygraphs')).draw(data, null);
       }
       google.setOnLoadCallback(drawVisualization);
index f1aff26..95900e1 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
   </head>
   <body>
     <script type="text/javascript" src="../dygraph.js"></script>
   </head>
   <body>
@@ -18,7 +19,7 @@
     <div id="gs" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="gs" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
             document.getElementById("g"),
             function HourlyData() {
               return "" +
             document.getElementById("g"),
             function HourlyData() {
               return "" +
               "2009/07/12 04:00:00,4,7\n" +
               "2009/07/12 05:00:00,3,6\n" +
               "2009/07/12 06:00:00,4,6"
               "2009/07/12 04:00:00,4,7\n" +
               "2009/07/12 05:00:00,3,6\n" +
               "2009/07/12 06:00:00,4,6"
-            }, null, {}
+            }
           );
 
           );
 
-      gm = new DateGraph(
+      gm = new Dygraph(
             document.getElementById("gm"),
             function() {
               var ret = "Date,Hours,Minutes\n";
             document.getElementById("gm"),
             function() {
               var ret = "Date,Hours,Minutes\n";
                 }
               }
               return ret;
                 }
               }
               return ret;
-            }, null, {}
+            }
           );
 
           );
 
-      gs = new DateGraph(
+      gs = new Dygraph(
             document.getElementById("gs"),
             function() {
               var ret = "Date,Minutes,Seconds\n";
             document.getElementById("gs"),
             function() {
               var ret = "Date,Minutes,Seconds\n";
@@ -64,7 +65,7 @@
                 }
               }
               return ret;
                 }
               }
               return ret;
-            }, null, {}
+            }
           );
     </script>
   </body>
           );
     </script>
   </body>
diff --git a/tests/label-div.html b/tests/label-div.html
new file mode 100644 (file)
index 0000000..cfda39e
--- /dev/null
@@ -0,0 +1,29 @@
+<html>
+  <head>
+    <title>noise</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+    <script type="text/javascript" src="data.js"></script>
+  </head>
+  <body>
+    <p>Chart with labels displayed in a separate div:</p>
+    <table><tr>
+    <td valign="top"><div id="graphdiv2"></div></td>
+    <td valign="top">&nbsp; &nbsp;</td>
+    <td valign="top"><div id="labels"></div></td>
+    </tr></table>
+
+    <script type="text/javascript">
+      g2 = new Dygraph(document.getElementById("graphdiv2"),
+                       data_nolabel,
+                       {
+                         labels: [ "High", "Low" ],
+                         labelsDiv: document.getElementById("labels")
+                       });
+    </script>
+  </body>
+</html>
diff --git a/tests/native-format.html b/tests/native-format.html
new file mode 100644 (file)
index 0000000..09a5235
--- /dev/null
@@ -0,0 +1,39 @@
+<html>
+  <head>
+    <title>Native Format</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+  </head>
+  <body>
+    <p>These two charts should be indistinguishable:</p>
+    <!-- <div id="graphdiv"></div> -->
+    <div id="graphdiv"></div>
+    <script type="text/javascript">
+      g = new Dygraph(document.getElementById("graphdiv"),
+                                    "x,A,B\n" +
+                                    "1,10,100\n" +
+                                    "2,20,80\n" +
+                                    "3,50,60\n" +
+                                    "4,70,80\n");
+    </script>
+
+    <p>Same data, specified in a parsed format:</p>
+    <div id="graphdiv2"></div>
+    <script type="text/javascript">
+      g2 = new Dygraph(document.getElementById("graphdiv2"),
+                       [ 
+                         [1,10,100],
+                         [2,20,80],
+                         [3,50,60],
+                         [4,70,80],
+                       ],
+                       {
+                         labels: [ "A", "B" ]
+                       });
+    </script>
+  </body>
+</html>
index ed46844..b6fcc25 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <div id="g30" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="g30" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
             document.getElementById("g"),
             document.getElementById("g"),
-            NoisyData, null, {
+            NoisyData, {
               rollPeriod: 7,
               errorBars: true
             }
           );
               rollPeriod: 7,
               errorBars: true
             }
           );
-      g30 = new DateGraph(
+      g30 = new Dygraph(
             document.getElementById("g30"),
             document.getElementById("g30"),
-            NoisyData, null, {
+            NoisyData, {
               rollPeriod: 14,
               errorBars: true
             }
               rollPeriod: 14,
               errorBars: true
             }
index ad8d9e6..f300f5f 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="http://www.google.com/jsapi"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="http://www.google.com/jsapi"></script>
   </head>
@@ -16,7 +17,7 @@
     <div id="gviz" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="gviz" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
             document.getElementById("g"),
             function() {
               var ret = "X,Y1,Y2\n";
             document.getElementById("g"),
             function() {
               var ret = "X,Y1,Y2\n";
                 ret += i + "," + i + "," + (i * (100-i) * 100/(50*50)) + "\n";
               }
               return ret;
                 ret += i + "," + i + "," + (i * (100-i) * 100/(50*50)) + "\n";
               }
               return ret;
-            }, null,
-            {
-              xValueParser: function(x) { return parseFloat(x); },
-              xValueFormatter: function(x) { return x; },
-              xTicker: DateGraph.prototype.numericTicks
-            }
+            },
+            { }
           );
 
       google.load('visualization', '1', {packages: ['linechart']});
           );
 
       google.load('visualization', '1', {packages: ['linechart']});
           data.setCell(i, 2, i * (100-i) * 100/(50*50));
         }
 
           data.setCell(i, 2, i * (100-i) * 100/(50*50));
         }
 
-        new DateGraph.GVizChart(
+        new Dygraph.GVizChart(
           document.getElementById('gviz')).draw(data,
           {
           document.getElementById('gviz')).draw(data,
           {
-            xValueParser: function(x) { return parseFloat(x); },
-            xValueFormatter: function(x) { return x; },
-            xTicker: DateGraph.prototype.numericTicks
           });
       }
 
           });
       }
 
index 8113d21..f249dcb 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <div id="g2" style="width:300px; height:200px;"></div>
 
     <script type="text/javascript">
     <div id="g2" style="width:300px; height:200px;"></div>
 
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
             document.getElementById("g"),
             document.getElementById("g"),
-            data, null, {
+            data, {
               rollPeriod: 7,
               pixelsPerYLabel: 20
             }
           );
               rollPeriod: 7,
               pixelsPerYLabel: 20
             }
           );
-      g2 = new DateGraph(
+      g2 = new Dygraph(
             document.getElementById("g2"),
             document.getElementById("g2"),
-            data, null, {
+            data, {
               rollPeriod: 7,
               pixelsPerYLabel: 20
             }
               rollPeriod: 7,
               pixelsPerYLabel: 20
             }
index 93fdbe7..0ce6134 100644 (file)
@@ -5,6 +5,7 @@
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
     <script type="text/javascript" src="excanvas.js"></script>
     <![endif]-->
     <script type="text/javascript" src="../dygraph-combined.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <script type="text/javascript" src="../dygraph.js"></script>
     <script type="text/javascript" src="data.js"></script>
   </head>
     <div id="g30" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
     <div id="g30" style="width:600px; height:300px;"></div>
 
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
             document.getElementById("g"),
             document.getElementById("g"),
-            data, null, {}
+            data, {}
           );
           );
-      g30 = new DateGraph(
+      g30 = new Dygraph(
             document.getElementById("g30"),
             document.getElementById("g30"),
-            data, null, {
+            data, {
               rollPeriod: 30
             }
           );
               rollPeriod: 30
             }
           );