Fix a bug involving calls to updateOptions() while panning.
authorDan Vanderkam <dan@dygraphs.com>
Sun, 4 Mar 2012 14:46:37 +0000 (09:46 -0500)
committerDan Vanderkam <dan@dygraphs.com>
Sun, 4 Mar 2012 14:46:37 +0000 (09:46 -0500)
This showed up with data that gets updated periodically, i.e. the once/second
updates in dynamic-update.html. Previously the chart would disappear whenever
an update occurred.

auto_tests/misc/local.html
auto_tests/tests/error_bars.js
auto_tests/tests/update_while_panning.js [new file with mode: 0644]
dygraph-interaction-model.js

index 8e4cfdd..ae39e7f 100644 (file)
@@ -36,6 +36,7 @@
   <script type="text/javascript" src="../tests/pathological_cases.js"></script>
   <script type="text/javascript" src="../tests/date_formats.js"></script>
   <script type="text/javascript" src="../tests/formats.js"></script>
+  <script type="text/javascript" src="../tests/update_while_panning.js"></script>
   <script type="text/javascript" src="../tests/update_options.js"></script>
   <script type="text/javascript" src="../tests/utils_test.js"></script>
   <script type="text/javascript" src="../tests/multiple_axes.js"></script>
index 0d8b3ba..8bc20e3 100644 (file)
@@ -21,7 +21,7 @@ errorBarsTestCase.prototype.tearDown = function() {
   Dygraph.getContext = _origFunc;
 };
 
-errorBarsTestCase.prototype.testNameGoesHere = function() {
+errorBarsTestCase.prototype.testErrorBarsDrawn = function() {
   var opts = {
     width: 480,
     height: 320,
diff --git a/auto_tests/tests/update_while_panning.js b/auto_tests/tests/update_while_panning.js
new file mode 100644 (file)
index 0000000..8634bd0
--- /dev/null
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Regression test for a bug involving data update while panning.
+ *
+ * See http://stackoverflow.com/questions/9528173
+ *
+ * @author dan@dygraphs.com (Dan Vanderkam)
+ */
+var updateWhilePanningTestCase = TestCase("update-while-panning");
+
+updateWhilePanningTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+updateWhilePanningTestCase.prototype.tearDown = function() {
+};
+
+// This tests the following sequence:
+// 1. Begin dragging a chart (x-panning)
+// 2. Do a data update (updateOptions({file: ...}))
+// 3. Verify that the y-axis is still well-defined.
+updateWhilePanningTestCase.prototype.testUpdateWhilePanning = function() {
+  var sinewave = function(start, limit, step) {
+    var data = [];
+    for (var x = start; x < limit; x += step) {
+      data.push([x, Math.sin(x)]);
+    }
+    return data;
+  };
+
+  var opts = {
+    width: 480,
+    height: 320,
+    valueRange: [-2, 2]
+  };
+
+  var graph = document.getElementById("graph");
+
+  var g = new Dygraph(graph, sinewave(0, 6, 0.1), opts);
+  assertEquals([-2, 2], g.yAxisRange());
+
+  // Start a pan, but don't finish it yet.
+  DygraphOps.dispatchMouseDown_Point(g, 200, 100, {shiftKey: true});
+  DygraphOps.dispatchMouseMove_Point(g, 100, 100, {shiftKey: true});
+  assertEquals([-2, 2], g.yAxisRange());
+
+  // Now do a data update. y-axis should remain the same.
+  g.updateOptions({file: sinewave(0, 7, 0.1)});
+  assertEquals([-2, 2], g.yAxisRange());
+
+  // Keep the pan going.
+  DygraphOps.dispatchMouseMove_Point(g, 50, 100, {shiftKey: true});
+  assertEquals([-2, 2], g.yAxisRange());
+
+  // Now finish the pan.
+  DygraphOps.dispatchMouseUp_Point(g, 100, 100, {shiftKey: true});
+  assertEquals([-2, 2], g.yAxisRange());
+};
+
index a3de6f7..c57ad25 100644 (file)
@@ -72,20 +72,26 @@ Dygraph.Interaction.startPan = function(event, g, context) {
 
   // Record the range of each y-axis at the start of the drag.
   // If any axis has a valueRange or valueWindow, then we want a 2D pan.
+  // We can't store data directly in g.axes_, because it does not belong to us
+  // and could change out from under us during a pan (say if there's a data
+  // update).
   context.is2DPan = false;
+  context.axes = [];
   for (i = 0; i < g.axes_.length; i++) {
     axis = g.axes_[i];
+    var axis_data = {};
     var yRange = g.yAxisRange(i);
     // TODO(konigsberg): These values should be in |context|.
     // In log scale, initialTopValue, dragValueRange and unitsPerPixel are log scale.
     if (axis.logscale) {
-      axis.initialTopValue = Dygraph.log10(yRange[1]);
-      axis.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]);
+      axis_data.initialTopValue = Dygraph.log10(yRange[1]);
+      axis_data.dragValueRange = Dygraph.log10(yRange[1]) - Dygraph.log10(yRange[0]);
     } else {
-      axis.initialTopValue = yRange[1];
-      axis.dragValueRange = yRange[1] - yRange[0];
+      axis_data.initialTopValue = yRange[1];
+      axis_data.dragValueRange = yRange[1] - yRange[0];
     }
-    axis.unitsPerPixel = axis.dragValueRange / (g.plotter_.area.h - 1);
+    axis_data.unitsPerPixel = axis_data.dragValueRange / (g.plotter_.area.h - 1);
+    context.axes.push(axis_data);
 
     // While calculating axes, set 2dpan.
     if (axis.valueWindow || axis.valueRange) context.is2DPan = true;
@@ -130,23 +136,24 @@ Dygraph.Interaction.movePan = function(event, g, context) {
     // Adjust each axis appropriately.
     for (var i = 0; i < g.axes_.length; i++) {
       var axis = g.axes_[i];
+      var axis_data = context.axes[i];
 
       var pixelsDragged = context.dragEndY - context.dragStartY;
-      var unitsDragged = pixelsDragged * axis.unitsPerPixel;
+      var unitsDragged = pixelsDragged * axis_data.unitsPerPixel;
 
       var boundedValue = context.boundedValues ? context.boundedValues[i] : null;
 
       // In log scale, maxValue and minValue are the logs of those values.
-      var maxValue = axis.initialTopValue + unitsDragged;
+      var maxValue = axis_data.initialTopValue + unitsDragged;
       if (boundedValue) {
         maxValue = Math.min(maxValue, boundedValue[1]);
       }
-      var minValue = maxValue - axis.dragValueRange;
+      var minValue = maxValue - axis_data.dragValueRange;
       if (boundedValue) {
         if (minValue < boundedValue[0]) {
           // Adjust maxValue, and recompute minValue.
           maxValue = maxValue - (minValue - boundedValue[0]);
-          minValue = maxValue - axis.dragValueRange;
+          minValue = maxValue - axis_data.dragValueRange;
         }
       }
       if (axis.logscale) {
@@ -186,8 +193,6 @@ Dygraph.Interaction.endPan = function(event, g, context) {
     Dygraph.Interaction.treatMouseOpAsClick(g, event, context);
   }
 
-  // TODO(konigsberg): Clear the context data from the axis.
-  // (replace with "context = {}" ?)
   // TODO(konigsberg): mouseup should just delete the
   // context object, and mousedown should create a new one.
   context.isPanning = false;
@@ -197,6 +202,7 @@ Dygraph.Interaction.endPan = function(event, g, context) {
   context.valueRange = null;
   context.boundedDates = null;
   context.boundedValues = null;
+  context.axes = null;
 };
 
 /**