Rewrite numericTicks to consider chart height. Fixes issue 26.
[dygraphs.git] / dygraph.js
index f5b5d72..e49c935 100644 (file)
@@ -73,6 +73,19 @@ DateGraph.DEFAULT_HEIGHT = 320;
 DateGraph.DEFAULT_STROKE_WIDTH = 1.0;
 DateGraph.AXIS_LINE_WIDTH = 0.3;
 
+// Default attribute values.
+DateGraph.DEFAULT_ATTRS = {
+  highlightCircleSize: 3,
+  pixelsPerXLabel: 60,
+  pixelsPerYLabel: 30,
+  labelsDivWidth: 250,
+  labelsDivStyles: {
+    // TODO(danvk): move defaults from createStatusMessage_ here.
+  }
+
+  // TODO(danvk): default padding
+};
+
 /**
  * Initializes the DateGraph. This creates a new DIV and constructs the PlotKit
  * and interaction <canvas> inside of it. See the constructor for details
@@ -100,7 +113,6 @@ DateGraph.prototype.__init__ = function(div, file, labels, attrs) {
   this.labelsSeparateLines = attrs.labelsSeparateLines || false;
   this.labelsDiv_ = attrs.labelsDiv || null;
   this.labelsKMB_ = attrs.labelsKMB || false;
-  this.minTickSize_ = attrs.minTickSize || 0;
   this.xValueParser_ = attrs.xValueParser || DateGraph.prototype.dateParser;
   this.xValueFormatter_ = attrs.xValueFormatter ||
       DateGraph.prototype.dateString_;
@@ -108,7 +120,10 @@ DateGraph.prototype.__init__ = function(div, file, labels, attrs) {
   this.sigma_ = attrs.sigma || 2.0;
   this.wilsonInterval_ = attrs.wilsonInterval || true;
   this.customBars_ = attrs.customBars || false;
-  this.attrs_ = attrs;
+
+  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;
@@ -240,7 +255,7 @@ DateGraph.prototype.setColors_ = function(attrs) {
  */
 DateGraph.prototype.createStatusMessage_ = function(){
   if (!this.labelsDiv_) {
-    var divWidth = 250;
+    var divWidth = this.attrs_.labelsDivWidth;
     var messagestyle = { "style": {
       "position": "absolute",
       "fontSize": "14px",
@@ -251,6 +266,7 @@ DateGraph.prototype.createStatusMessage_ = function(){
       "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_);
   }
@@ -484,7 +500,7 @@ DateGraph.prototype.mouseMove_ = function(event) {
   }
 
   // Clear the previously drawn vertical, if there is one
-  var circleSize = 3;
+  var circleSize = this.attrs_.highlightCircleSize;
   var ctx = this.canvas_.getContext("2d");
   if (this.previousVerticalX_ >= 0) {
     var px = this.previousVerticalX_;
@@ -768,26 +784,34 @@ DateGraph.prototype.dateTicker = function(startDate, endDate) {
  * @public
  */
 DateGraph.prototype.numericTicks = function(minV, maxV) {
-  var scale;
-  if (maxV <= 0.0) {
-    scale = 1.0;
-  } else {
-    scale = Math.pow( 10, Math.floor(Math.log(maxV)/Math.log(10.0)) );
-  }
-
-  // Add a smallish number of ticks at human-friendly points
-  var nTicks = (maxV - minV) / scale;
-  while (2 * nTicks < 20) {
-    nTicks *= 2;
-  }
-  if ((maxV - minV) / nTicks < this.minTickSize_) {
-    nTicks = this.round_((maxV - minV) / this.minTickSize_, 1);
+  // 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.
+  var mults = [1, 2, 5];
+  var scale, low_val, high_val, nTicks;
+  for (var i = -10; i < 50; i++) {
+    var base_scale = Math.pow(10, i);
+    for (var j = 0; j < mults.length; j++) {
+      scale = base_scale * mults[j];
+      console.log("i/j/scale: " + i + "/" + j + "/" + 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;
+      // wish I could break out of both loops at once...
+      if (spacing > this.attrs_.pixelsPerYLabel) break;
+    }
+    if (spacing > this.attrs_.pixelsPerYLabel) break;
   }
+  console.log("scale: " + scale);
+  console.log("low_val: " + low_val);
+  console.log("high_val: " + high_val);
 
   // Construct labels for the ticks
   var ticks = [];
-  for (var i = 0; i <= nTicks; i++) {
-    var tickV = minV + i * (maxV - minV) / nTicks;
+  for (var i = 0; i < nTicks; i++) {
+    var tickV = low_val + i * scale;
     var label = this.round_(tickV, 2);
     if (this.labelsKMB_) {
       var k = 1000;
@@ -879,10 +903,6 @@ DateGraph.prototype.drawGraph_ = function(data) {
     // Add some padding and round up to an integer to be human-friendly.
     maxY *= 1.1;
     if (maxY <= 0.0) maxY = 1.0;
-    else {
-      var scale = Math.pow(10, Math.floor(Math.log(maxY) / Math.log(10.0)));
-      maxY = scale * Math.ceil(maxY / scale);
-    }
     this.addYTicks_(0, maxY);
   }
 
@@ -1225,9 +1245,7 @@ DateGraph.prototype.updateOptions = function(attrs) {
   if (attrs.valueRange) {
     this.valueRange_ = attrs.valueRange;
   }
-  if (attrs.minTickSize) {
-    this.minTickSize_ = attrs.minTickSize;
-  }
+  MochiKit.Base.update(this.attrs_, attrs);
   if (typeof(attrs.labels) != 'undefined') {
     this.labels_ = attrs.labels;
     this.labelsFromCSV_ = (attrs.labels == null);