Merge branch 'master' of http://github.com/danvk/dygraphs
authorNeal Nelson <neal@makalumedia.com>
Mon, 31 Jan 2011 13:08:52 +0000 (14:08 +0100)
committerNeal Nelson <neal@makalumedia.com>
Mon, 31 Jan 2011 13:08:52 +0000 (14:08 +0100)
Conflicts:
dygraph.js

docs/index.html
dygraph.js
tests/no-zoom-change.html [new file with mode: 0644]
tests/zoom.html

index f8ad937..400478b 100644 (file)
@@ -447,6 +447,85 @@ new Dygraph(el, data, {
       <li>Where the error bars do not overlap, we can say with 95% confidence that the series differ. There is a better than 95% chance that Ichiro was a better hitter than his team as a whole in 2004, the year he won the batting title.</li>
     </ul>
 
+    <h2 id="zoom">Determining Zoom</h2>
+    
+    <p>
+      It is possible to detect whether a chart has been zoomed in either axis by the use of the <code>isZoomed</code> function.
+      If called with no argument, it will report whether <em>either</em> axis has been zoomed.
+      Alternatively it can be called with an argument of either <code>'x'</code> or <code>'y'</code> and it will report the status of just that axis.
+    </p>
+
+    <p>Here's a simple example using <code>drawCallback</code> to display the various zoom states whenever the chart is zoomed:</p>
+
+    <div style="width:600px; text-align:center; font-weight: bold; font-size: 125%;">OUTPUT</div>
+    <div style="width: 750px">
+      <div style="float: right">
+          <p>Zoomed: <span id="zoomed">False</span><p/>
+          <p>Zoomed X: <span id="zoomedX">False</span><p/>
+          <p>Zoomed Y: <span id="zoomedY">False</span><p/>
+      </div>
+      <div class="codeoutput" style="float:left;">
+        <div id="zoomdiv"></div>
+        <script type="text/javascript">
+          new Dygraph(
+
+            // containing div
+            document.getElementById("zoomdiv"),
+
+            // CSV or path to a CSV file.
+            "Date,Value\n" +
+            "2011-01-07,75\n" +
+            "2011-01-08,70\n" +
+            "2011-01-09,90\n" +
+            "2011-01-10,30\n" +
+            "2011-01-11,40\n" +
+            "2011-01-12,60\n" +
+            "2011-01-13,70\n" +
+            "2011-01-14,40\n",
+            {
+              drawCallback: function(me, initial) {
+                document.getElementById("zoomed").innerHTML = "" + me.isZoomed();
+                document.getElementById("zoomedX").innerHTML = "" + me.isZoomed("x");
+                document.getElementById("zoomedY").innerHTML = "" + me.isZoomed("y");
+              }
+            }
+          );
+        </script>
+      </div>
+    </div>
+
+    <p>
+      <div style="clear:both; width:600px; text-align:center; font-weight: bold; font-size: 125%;">HTML</div>
+
+<pre>
+  new Dygraph(
+
+    // containing div
+    document.getElementById(&quot;zoomdiv&quot;),
+
+    // CSV or path to a CSV file.
+    &quot;Date,Temperature\n&quot; +
+    &quot;2011-01-07,75\n&quot; +
+    &quot;2011-01-08,70\n&quot; +
+    &quot;2011-01-09,90\n&quot; +
+    &quot;2011-01-10,30\n&quot; +
+    &quot;2011-01-11,40\n&quot; +
+    &quot;2011-01-12,60\n&quot; +
+    &quot;2011-01-13,70\n&quot; +
+    &quot;2011-01-14,40\n&quot;,
+    {
+      drawCallback: function(me, initial) {
+        document.getElementById(&quot;zoomed&quot;).innerHTML = &quot;&quot; + me.isZoomed();
+        document.getElementById(&quot;zoomedX&quot;).innerHTML = &quot;&quot; + me.isZoomed(&quot;x&quot;);
+        document.getElementById(&quot;zoomedY&quot;).innerHTML = &quot;&quot; + me.isZoomed(&quot;y&quot;);
+      }
+    }
+  );
+</pre>
+    </p>
+
+    <p>The <a href="tests/zoom.html">Tests for zoom operations</a> show a full example of this in action.</p>
+
     <h2 id="stock">One last demo</h2>
 
     <p>This chart shows monthly closes of the Dow Jones Industrial Average, both in nominal and real (i.e. adjusted for inflation) dollars. The shaded areas show its monthly high and low. CPI values with a base from 1982-84 are used to adjust for inflation.</p>
@@ -730,7 +809,7 @@ perl -ne 'BEGIN{print "Month,Nominal,Real\n"} chomp; ($m,$cpi,$low,$close,$high)
         </tr>
         <tr>
           <td><strong>pointSize</strong></td>
-          <td><code>interger</code></td>
+          <td><code>integer</code></td>
           <td><code>1</code></td>
           <td>The size of the dot to draw on each point in pixels (see drawPoints). A dot is always drawn when a point is "isolated", i.e. there is a missing point on either side of it. This also controls the size of those dots.
           <div class="tests">Tests: <font color=red>none</font></div>
@@ -946,6 +1025,17 @@ perl -ne 'BEGIN{print "Month,Nominal,Real\n"} chomp; ($m,$cpi,$low,$close,$high)
           </td>
         </tr>
 
+        <tr>
+          <td><strong>noZoomFlagChange</strong></td>
+          <td><code></code></td>
+          <td><code></code></td>
+          <td>
+            When this flag is passed along with either the <code>dateWindow</code> or <code>valueRange</code> options, the zoom flags are not changed to reflect a zoomed state.
+            This is primarily useful for when the display area of a chart is changed programmatically and also where manual zooming is allowed and use is made of the <code>isZoomed</code> method to determine this.
+            <div class="tests">Tests: <a href="tests/no-zoom-change.html">no-zoom-change</a></div>
+          </td>
+        </tr>
+
       </tbody>
     </table>
 
index 0989a1d..646159a 100644 (file)
@@ -195,6 +195,10 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.is_initial_draw_ = true;
   this.annotations_ = [];
 
+  // Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
+  this.zoomed_x_ = false;
+  this.zoomed_y_ = false;
+
   // Clear the div. This ensure that, if multiple dygraphs are passed the same
   // div, then only one will be drawn.
   div.innerHTML = "";
@@ -257,6 +261,14 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.start_();
 };
 
+// axis is an optional parameter. Can be set to 'x' or 'y'.
+Dygraph.prototype.isZoomed = function(axis) {
+  if (axis == null) return this.zoomed_x_ || this.zoomed_y_;
+  if (axis == 'x') return this.zoomed_x_;
+  if (axis == 'y') return this.zoomed_y_;
+  throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
+};
+
 Dygraph.prototype.attr_ = function(name, seriesName) {
   if (seriesName &&
       typeof(this.user_attrs_[seriesName]) != 'undefined' &&
@@ -1192,6 +1204,7 @@ Dygraph.prototype.doZoomX_ = function(lowX, highX) {
  */
 Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) {
   this.dateWindow_ = [minDate, maxDate];
+  this.zoomed_x_ = true;
   this.drawGraph_();
   if (this.attr_("zoomCallback")) {
     this.attr_("zoomCallback")(minDate, maxDate, this.yAxisRanges());
@@ -1219,9 +1232,11 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) {
     valueRanges.push([low[1], hi[1]]);
   }
 
+  this.zoomed_y_ = true;
   this.drawGraph_();
   if (this.attr_("zoomCallback")) {
     var xRange = this.xAxisRange();
+    var yRange = this.yAxisRange();
     this.attr_("zoomCallback")(xRange[0], xRange[1], this.yAxisRanges());
   }
 };
@@ -1249,6 +1264,8 @@ Dygraph.prototype.doUnzoom_ = function() {
   if (dirty) {
     // Putting the drawing operation before the callback because it resets
     // yAxisRange.
+    this.zoomed_x_ = false;
+    this.zoomed_y_ = false;
     this.drawGraph_();
     if (this.attr_("zoomCallback")) {
       var minDate = this.rawData_[0][0];
@@ -2083,18 +2100,22 @@ Dygraph.prototype.drawGraph_ = function() {
     this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
   }
 
-  // TODO(danvk): this method doesn't need to return anything.
-  var out = this.computeYAxisRanges_(extremes);
-  var axes = out[0];
-  var seriesToAxisMap = out[1];
-  this.layout_.updateOptions( { yAxes: axes,
-                                seriesToAxisMap: seriesToAxisMap
-                              } );
-
+  if (datasets.length > 0) {
+    // TODO(danvk): this method doesn't need to return anything.
+    var out = this.computeYAxisRanges_(extremes);
+    var axes = out[0];
+    var seriesToAxisMap = out[1];
+    this.layout_.updateOptions( { yAxes: axes,
+                                  seriesToAxisMap: seriesToAxisMap
+                                } );
+  }
   this.addXTicks_();
 
+  // Save the X axis zoomed status as the updateOptions call will tend to set it errorneously
+  var tmp_zoomed_x = this.zoomed_x_;
   // Tell PlotKit to use this new data and render itself
   this.layout_.updateOptions({dateWindow: this.dateWindow_});
+  this.zoomed_x_ = tmp_zoomed_x;
   this.layout_.evaluateWithError();
   this.plotter_.clear();
   this.plotter_.render();
@@ -2117,6 +2138,15 @@ Dygraph.prototype.drawGraph_ = function() {
  *   indices are into the axes_ array.
  */
 Dygraph.prototype.computeYAxes_ = function() {
+  var valueWindows;
+  if (this.axes_ != undefined) {
+    // Preserve valueWindow settings.
+    valueWindows = [];
+    for (var index = 0; index < this.axes_.length; index++) {
+      valueWindows.push(this.axes_[index].valueWindow);
+    }
+  }
+
   this.axes_ = [{ yAxisId: 0 }];  // always have at least one y-axis.
   this.seriesToAxisMap_ = {};
 
@@ -2191,6 +2221,13 @@ Dygraph.prototype.computeYAxes_ = function() {
     if (vis[i - 1]) seriesToAxisFiltered[s] = this.seriesToAxisMap_[s];
   }
   this.seriesToAxisMap_ = seriesToAxisFiltered;
+
+  if (valueWindows != undefined) {
+    // Restore valueWindow settings.
+    for (var index = 0; index < valueWindows.length; index++) {
+      this.axes_[index].valueWindow = valueWindows[index];
+    }
+  }
 };
 
 /**
@@ -2886,6 +2923,12 @@ Dygraph.prototype.updateOptions = function(attrs) {
   }
   if ('dateWindow' in attrs) {
     this.dateWindow_ = attrs.dateWindow;
+    if (!('noZoomFlagChange' in attrs)) {
+      this.zoomed_x_ = attrs.dateWindow != null;
+    }
+  }
+  if ('valueRange' in attrs && !('noZoomFlagChange' in attrs)) {
+    this.zoomed_y_ = attrs.valueRange != null;
   }
 
   // TODO(danvk): validate per-series options.
diff --git a/tests/no-zoom-change.html b/tests/no-zoom-change.html
new file mode 100644 (file)
index 0000000..e1b93d0
--- /dev/null
@@ -0,0 +1,132 @@
+<html>
+    <head>
+        <title>zoom</title>
+        <!--[if IE]>
+        <script type="text/javascript" src="../excanvas.js"></script>
+        <![endif]-->
+        <script type="text/javascript" src="../strftime/strftime-min.js"></script>
+        <script type="text/javascript" src="../rgbcolor/rgbcolor.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>
+        <h3>Click the buttons to change the date and value windows of the chart.</h3>
+        <h4>Window coordinates (in dates and values):</h4>
+        <div id="xdimensions"></div> <div id="ydimensions"></div>
+        <br />
+        <div>
+            <p>
+                Toggle the check box below to determine the difference in operation of the zoom flags
+                when the date and value windows of the chart are changed using the arrows underneath.
+            </p>
+            <input id="noZoomFlagChange" type="checkbox" checked=true>Do not change zoom
+        </div>
+
+        <div style="float: right">
+            <p>Zoomed: <span id="zoomed">False</span><p/>
+            <p>Zoomed X: <span id="zoomedX">False</span><p/>
+            <p>Zoomed Y: <span id="zoomedY">False</span><p/>
+        </div>
+        <div style="float: left">
+            <p><input type="button" value="&uarr;" onclick="adjustTop(+1)"></p>
+            <p><input type="button" value="&darr;" onclick="adjustTop(-1)"></p>
+            <p>
+                <input type="button" value="&larr;" onclick="adjustFirst(-100000000)">
+                    <input type="button" value="&rarr;" onclick="adjustFirst(+100000000)">
+            </p>
+            <p><input type="button" value="&uarr;" onclick="adjustBottom(+1)"></p>
+            <p><input type="button" value="&darr;" onclick="adjustBottom(-1)"></p>
+        </div>
+        <div id="div_g" style="width: 600px; height: 300px; float: left"></div>
+        <div style="float: left">
+            <p>
+                <input type="button" value="&larr;" onclick="adjustLast(-100000000)">
+                <input type="button" value="&rarr;" onclick="adjustLast(+100000000)">
+            </p>
+        </div>
+        <script type="text/javascript">
+            g = new Dygraph(
+                document.getElementById("div_g"),
+                NoisyData,
+                {
+                    errorBars: true,
+                    zoomCallback : function(minDate, maxDate, yRange) {
+                        showDimensions(minDate, maxDate, yRange);
+                    },
+                    drawCallback: function(me, initial) {
+                        document.getElementById("zoomed").innerHTML = "" + me.isZoomed();
+                        document.getElementById("zoomedX").innerHTML = "" + me.isZoomed("x");
+                        document.getElementById("zoomedY").innerHTML = "" + me.isZoomed("y");
+                        var x_range = me.xAxisRange()
+                        var elem = document.getElementById("xdimensions")
+                        elem.innerHTML = "dateWindow : [" + x_range[0] + ", "+ x_range[1] + "]"
+                    }
+                }
+            )
+
+            // Pull an initial value for logging.
+            var minDate = g.xAxisRange()[0];
+            var maxDate = g.xAxisRange()[1];
+            var minValue = g.yAxisRange()[0];
+            var maxValue = g.yAxisRange()[1];
+            showDimensions(minDate, maxDate, [minValue, maxValue]);
+
+            function showDimensions(minDate, maxDate, yRanges) {
+              showXDimensions(minDate, maxDate);
+              showYDimensions(yRanges);
+            }
+
+            function getNoChange() {
+                var options = {}
+                var elem = document.getElementById("noZoomFlagChange")
+                if (elem.checked) {
+                    options.noZoomFlagChange = true
+                }
+                return options
+            }
+
+            function adjustTop(value) {
+                options = getNoChange()
+                maxValue += value
+                options.valueRange = [minValue, maxValue]
+                console.log(options)
+                g.updateOptions(options)
+            }
+
+            function adjustBottom(value) {
+                options = getNoChange()
+                minValue += value
+                options.valueRange = [minValue, maxValue]
+                console.log(options)
+                g.updateOptions(options)
+            }
+
+            function adjustFirst(value) {
+                options = getNoChange()
+                minDate += value
+                options.dateWindow = [minDate, maxDate]
+                console.log(options)
+                g.updateOptions(options)
+            }
+
+            function adjustLast(value) {
+                options = getNoChange()
+                maxDate += value
+                options.dateWindow = [minDate, maxDate]
+                g.updateOptions(options)
+            }
+
+            function showXDimensions(first, second) {
+              var elem = document.getElementById("xdimensions");
+              elem.innerHTML = "dateWindow : [" + first + ", "+ second + "]";
+            }
+
+            function showYDimensions(ranges) {
+              var elem = document.getElementById("ydimensions");
+              elem.innerHTML = "valueRange : [" + ranges + "]";
+            }
+
+        </script>
+    </body>
+</html>
index ad0bc9d..91d3ee4 100644 (file)
     buttons are useful for testing.</h3>
     <h4>Window coordinates (in dates and values):</h4>
     <div id="xdimensions"></div> <div id="ydimensions"></div>
+    <div style="float: right">
+        <p>Zoomed: <span id="zoomed">False</span><p/>
+        <p>Zoomed X: <span id="zoomedX">False</span><p/>
+        <p>Zoomed Y: <span id="zoomedY">False</span><p/>
+    </div>
     <div id="div_g" style="width:600px; height:300px;"></div>
 
+
     <p><b>Zoom operations:</b></p>
     <p>
       <input type="button" value="Y (3,5)" onclick="zoomGraphY(3,5)">&nbsp;
             document.getElementById("div_g"),
             NoisyData, {
               errorBars: true,
-              zoomCallback : function(minDate, maxDate, yRanges) {
-                showDimensions(minDate, maxDate, yRanges);
+              zoomCallback : function(minDate, maxDate, yRange) {
+                showDimensions(minDate, maxDate, yRange);
+              },
+              drawCallback: function(me, initial) {
+                document.getElementById("zoomed").innerHTML = "" + me.isZoomed();
+                document.getElementById("zoomedX").innerHTML = "" + me.isZoomed("x");
+                document.getElementById("zoomedY").innerHTML = "" + me.isZoomed("y");
               }
             }
           );