switch to YUI compressor for making packed JS -- 49k -> 42k
[dygraphs.git] / dygraph.js
index 8c3bc59..8858396 100644 (file)
@@ -525,12 +525,15 @@ Dygraph.prototype.createDragInterface_ = function() {
   var self = this;
 
   // Tracks whether the mouse is down right now
-  var mouseDown = false;
+  var isZooming = false;
+  var isPanning = false;
   var dragStartX = null;
   var dragStartY = null;
   var dragEndX = null;
   var dragEndY = null;
   var prevEndX = null;
+  var draggingDate = null;
+  var dateRange = null;
 
   // Utility function to convert page-wide coordinates to canvas coords
   var px = 0;
@@ -540,37 +543,63 @@ Dygraph.prototype.createDragInterface_ = function() {
 
   // Draw zoom rectangles when the mouse is down and the user moves around
   Dygraph.addEvent(this.hidden_, 'mousemove', function(event) {
-    if (mouseDown) {
+    if (isZooming) {
       dragEndX = getX(event);
       dragEndY = getY(event);
 
       self.drawZoomRect_(dragStartX, dragEndX, prevEndX);
       prevEndX = dragEndX;
+    } else if (isPanning) {
+      dragEndX = getX(event);
+      dragEndY = getY(event);
+
+      // Want to have it so that:
+      // 1. draggingDate appears at dragEndX
+      // 2. daterange = (dateWindow_[1] - dateWindow_[0]) is unaltered.
+
+      self.dateWindow_[0] = draggingDate - (dragEndX / self.width_) * dateRange;
+      self.dateWindow_[1] = self.dateWindow_[0] + dateRange;
+      self.drawGraph_(self.rawData_);
     }
   });
 
   // Track the beginning of drag events
   Dygraph.addEvent(this.hidden_, 'mousedown', function(event) {
-    mouseDown = true;
     px = Dygraph.findPosX(self.canvas_);
     py = Dygraph.findPosY(self.canvas_);
     dragStartX = getX(event);
     dragStartY = getY(event);
+
+    if (event.altKey || event.shiftKey) {
+      if (!self.dateWindow_) return;  // have to be zoomed in to pan.
+      isPanning = true;
+      dateRange = self.dateWindow_[1] - self.dateWindow_[0];
+      draggingDate = (dragStartX / self.width_) * dateRange +
+        self.dateWindow_[0];
+    } else {
+      isZooming = true;
+    }
   });
 
   // If the user releases the mouse button during a drag, but not over the
   // canvas, then it doesn't count as a zooming action.
   Dygraph.addEvent(document, 'mouseup', function(event) {
-    if (mouseDown) {
-      mouseDown = false;
+    if (isZooming || isPanning) {
+      isZooming = false;
       dragStartX = null;
       dragStartY = null;
     }
+
+    if (isPanning) {
+      isPanning = false;
+      draggingDate = null;
+      dateRange = null;
+    }
   });
 
   // Temporarily cancel the dragging event when the mouse leaves the graph
   Dygraph.addEvent(this.hidden_, 'mouseout', function(event) {
-    if (mouseDown) {
+    if (isZooming) {
       dragEndX = null;
       dragEndY = null;
     }
@@ -579,8 +608,8 @@ Dygraph.prototype.createDragInterface_ = function() {
   // If the mouse is released on the canvas during a drag event, then it's a
   // zoom. Only do the zoom if it's over a large enough area (>= 10 pixels)
   Dygraph.addEvent(this.hidden_, 'mouseup', function(event) {
-    if (mouseDown) {
-      mouseDown = false;
+    if (isZooming) {
+      isZooming = false;
       dragEndX = getX(event);
       dragEndY = getY(event);
       var regionWidth = Math.abs(dragEndX - dragStartX);
@@ -605,6 +634,12 @@ Dygraph.prototype.createDragInterface_ = function() {
       dragStartX = null;
       dragStartY = null;
     }
+
+    if (isPanning) {
+      isPanning = false;
+      draggingDate = null;
+      dateRange = null;
+    }
   });
 
   // Double-clicking zooms back out
@@ -941,7 +976,7 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
   if (granularity < Dygraph.MONTHLY) {
     // Generate one tick mark for every fixed interval of time.
     var spacing = Dygraph.SHORT_SPACINGS[granularity];
-    var format = '%d%b';  // e.g. "1 Jan"
+    var format = '%d%b';  // e.g. "1Jan"
     // TODO(danvk): be smarter about making sure this really hits a "nice" time.
     if (granularity < Dygraph.HOURLY) {
       start_time = spacing * Math.floor(0.5 + start_time / spacing);
@@ -1030,12 +1065,20 @@ Dygraph.numericTicks = function(minV, maxV, self) {
   // 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 pixelsPerYLabel is what we use.
-  var mults = [1, 2, 5];
+  if (self.attr_("labelsKMG2")) {
+    var mults = [1, 2, 4, 8];
+  } else {
+    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);
+    if (self.attr_("labelsKMG2")) {
+      var base_scale = Math.pow(16, i);
+    } else {
+      var base_scale = Math.pow(10, i);
+    }
     for (var j = 0; j < mults.length; j++) {
       scale = base_scale * mults[j];
       low_val = Math.floor(minV / scale) * scale;
@@ -1122,7 +1165,7 @@ Dygraph.prototype.extremeValues_ = function(series) {
   } else {
     for (var j = 0; j < series.length; j++) {
       var y = series[j][1];
-      if (!y) continue;
+      if (y === null || isNaN(y)) continue;
       if (maxY == null || y > maxY) {
         maxY = y;
       }
@@ -1214,11 +1257,12 @@ Dygraph.prototype.drawGraph_ = function(data) {
   this.addXTicks_();
 
   // Tell PlotKit to use this new data and render itself
+  this.layout_.updateOptions({dateWindow: this.dateWindow_});
   this.layout_.evaluateWithError();
   this.plotter_.clear();
   this.plotter_.render();
-  this.canvas_.getContext('2d').clearRect(0, 0,
-                                         this.canvas_.width, this.canvas_.height);
+  this.canvas_.getContext('2d').clearRect(0, 0, this.canvas_.width,
+                                         this.canvas_.height);
 };
 
 /**
@@ -1601,7 +1645,13 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   var ret = [];
   for (var i = 0; i < rows; i++) {
     var row = [];
-    if (!data.getValue(i, 0)) continue;
+    if (typeof(data.getValue(i, 0)) === 'undefined' ||
+        data.getValue(i, 0) === null) {
+      this.warning("Ignoring row " + i +
+                   " of DataTable because of undefined or null first column.");
+      continue;
+    }
+
     if (indepType == 'date') {
       row.push(data.getValue(i, 0).getTime());
     } else {
@@ -1760,7 +1810,10 @@ Dygraph.prototype.resize = function(width, height) {
     width = height = null;
   }
 
+  // TODO(danvk): there should be a clear() method.
   this.maindiv_.innerHTML = "";
+  this.attrs_.labelsDiv = null;
+
   if (width) {
     this.maindiv_.style.width = width + "px";
     this.maindiv_.style.height = height + "px";