Fix for issue 112: off-by-one issues with panning.
authorRobert Konigsberg <konigsberg@google.com>
Thu, 27 Jan 2011 17:12:00 +0000 (12:12 -0500)
committerRobert Konigsberg <konigsberg@google.com>
Thu, 27 Jan 2011 17:12:00 +0000 (12:12 -0500)
This also fixes an annoying problem with y-axis panning where the closer
to the bottom a pan operation, the more severe an initial jagging occurs
along the y-axis.

The keys to this fix are a) an off-by-one error related to computing
widths and heights, and b) the use of the plotter area's dimensions
rather than the graph's dimensions (graph dimensions also account for
spaces for the tick marks and annotations; that doesn't help us very
well.)

dygraph.js

index 8741bf0..f1ecd42 100644 (file)
@@ -801,6 +801,8 @@ Dygraph.startPan = function(event, g, context) {
   context.isPanning = true;
   var xRange = g.xAxisRange();
   context.dateRange = xRange[1] - xRange[0];
+  context.initialLeftmostDate = xRange[0];
+  context.xUnitsPerPixel = context.dateRange / (g.plotter_.area.w - 1);
 
   // 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.
@@ -808,15 +810,12 @@ Dygraph.startPan = function(event, g, context) {
   for (var i = 0; i < g.axes_.length; i++) {
     var axis = g.axes_[i];
     var yRange = g.yAxisRange(i);
+    // TODO(konigsberg): These values should be in |context|.
     axis.dragValueRange = yRange[1] - yRange[0];
-    var r = g.toDataCoords(null, context.dragStartY, i);
-    axis.draggingValue = r[1];
+    axis.initialTopValue = yRange[1];
+    axis.unitsPerPixel = axis.dragValueRange / (g.plotter_.area.h - 1);
     if (axis.valueWindow || axis.valueRange) context.is2DPan = true;
   }
-
-  // TODO(konigsberg): Switch from all this math to toDataCoords?
-  // Seems to work for the dragging value.
-  context.draggingDate = (context.dragStartX / g.width_) * context.dateRange + xRange[0];
 };
 
 // Called in response to an interaction model operation that
@@ -830,14 +829,8 @@ Dygraph.movePan = function(event, g, context) {
   context.dragEndX = g.dragGetX_(event, context);
   context.dragEndY = g.dragGetY_(event, context);
 
-  // TODO(danvk): update this comment
-  // Want to have it so that:
-  // 1. draggingDate appears at dragEndX, draggingValue appears at dragEndY.
-  // 2. daterange = (dateWindow_[1] - dateWindow_[0]) is unaltered.
-  // 3. draggingValue appears at dragEndY.
-  // 4. valueRange is unaltered.
-
-  var minDate = context.draggingDate - (context.dragEndX / g.width_) * context.dateRange;
+  var minDate = context.initialLeftmostDate -
+    (context.dragEndX - context.dragStartX) * context.xUnitsPerPixel;
   var maxDate = minDate + context.dateRange;
   g.dateWindow_ = [minDate, maxDate];
 
@@ -847,7 +840,8 @@ Dygraph.movePan = function(event, g, context) {
     var y_frac = context.dragEndY / g.height_;
     for (var i = 0; i < g.axes_.length; i++) {
       var axis = g.axes_[i];
-      var maxValue = axis.draggingValue + y_frac * axis.dragValueRange;
+      var maxValue = axis.initialTopValue +
+        (context.dragEndY - context.dragStartY) * axis.unitsPerPixel;
       var minValue = maxValue - axis.dragValueRange;
       axis.valueWindow = [ minValue, maxValue ];
     }
@@ -864,9 +858,12 @@ Dygraph.movePan = function(event, g, context) {
 // panning behavior.
 //
 Dygraph.endPan = function(event, g, context) {
+  // TODO(konigsberg): Clear the context data from the axis.
+  // TODO(konigsberg): mouseup should just delete the
+  // context object, and mousedown should create a new one.
   context.isPanning = false;
   context.is2DPan = false;
-  context.draggingDate = null;
+  context.initialLeftmostDate = null;
   context.dateRange = null;
   context.valueRange = null;
 }
@@ -1042,12 +1039,12 @@ Dygraph.prototype.createDragInterface_ = function() {
     prevEndY: null,
     prevDragDirection: null,
 
-    // TODO(danvk): update this comment
-    // draggingDate and draggingValue represent the [date,value] point on the
-    // graph at which the mouse was pressed. As the mouse moves while panning,
-    // the viewport must pan so that the mouse position points to
-    // [draggingDate, draggingValue]
-    draggingDate: null,
+    // The value on the left side of the graph when a pan operation starts.
+    initialLeftmostDate: null,
+
+    // The number of units each pixel spans. (This won't be valid for log
+    // scales)
+    xUnitsPerPixel: null,
 
     // TODO(danvk): update this comment
     // The range in second/value units that the viewport encompasses during a