Bug because I forgot a 'this' and also two comment tweaks.
[dygraphs.git] / dygraph.js
index dd4e7b5..6ab7606 100644 (file)
@@ -79,6 +79,11 @@ Dygraph.DEFAULT_WIDTH = 480;
 Dygraph.DEFAULT_HEIGHT = 320;
 Dygraph.AXIS_LINE_WIDTH = 0.3;
 
+Dygraph.LOG_SCALE = 10;
+Dygraph.LOG_BASE_E_OF_TEN = Math.log(Dygraph.LOG_SCALE);
+Dygraph.log10 = function(x) {
+  return Math.log(x) / Dygraph.LOG_BASE_E_OF_TEN;
+}
 
 // Default attribute values.
 Dygraph.DEFAULT_ATTRS = {
@@ -129,8 +134,8 @@ Dygraph.DEFAULT_ATTRS = {
 
   stepPlot: false,
   avoidMinZero: false,
-  interactionModel: null // will be set to Dygraph.defaultInteractionModel.
+
+  interactionModel: null  // will be set to Dygraph.defaultInteractionModel.
 };
 
 // Various logging levels.
@@ -356,44 +361,149 @@ Dygraph.prototype.yAxisRanges = function() {
  * If specified, do this conversion for the coordinate system of a particular
  * axis. Uses the first axis by default.
  * Returns a two-element array: [X, Y]
+ *
+ * Note: use toDomXCoord instead of toDomCoords(x, null) and use toDomYCoord
+ * instead of toDomCoords(null, y, axis).
  */
 Dygraph.prototype.toDomCoords = function(x, y, axis) {
-  var ret = [null, null];
+  return [ this.toDomXCoord(x), this.toDomYCoord(y, axis) ];
+};
+
+/**
+ * Convert from data x coordinates to canvas/div X coordinate.
+ * If specified, do this conversion for the coordinate system of a particular
+ * axis. Uses the first axis by default.
+ * returns a single value or null if x is null.
+ */
+Dygraph.prototype.toDomXCoord = function(x) {
+  if (x == null) {
+    return null;
+  };
+
   var area = this.plotter_.area;
-  if (x !== null) {
-    var xRange = this.xAxisRange();
-    ret[0] = area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
-  }
+  var xRange = this.xAxisRange();
+  return area.x + (x - xRange[0]) / (xRange[1] - xRange[0]) * area.w;
+}
 
-  if (y !== null) {
-    var yRange = this.yAxisRange(axis);
-    ret[1] = area.y + (yRange[1] - y) / (yRange[1] - yRange[0]) * area.h;
-  }
+/**
+ * Convert from data x coordinates to canvas/div Y coordinate and optional
+ * axis. Uses the first axis by default.
+ *
+ * returns a single value or null if y is null.
+ */
+Dygraph.prototype.toDomYCoord = function(y, axis) {
+  var pct = this.toPercentYCoord(y, axis);
 
-  return ret;
-};
+  if (pct == null) {
+    return null;
+  }
+  return area.y + pct * area.h;
+}
 
 /**
  * Convert from canvas/div coords to data coordinates.
  * If specified, do this conversion for the coordinate system of a particular
  * axis. Uses the first axis by default.
- * Returns a two-element array: [X, Y]
+ * Returns a two-element array: [X, Y].
+ *
+ * Note: use toDataXCoord instead of toDataCoords(x, null) and use toDataYCoord
+ * instead of toDataCoords(null, y, axis).
  */
 Dygraph.prototype.toDataCoords = function(x, y, axis) {
-  var ret = [null, null];
+  return [ this.toDataXCoord(x), this.toDataYCoord(y, axis) ];
+};
+
+/**
+ * Convert from canvas/div x coordinate to data coordinate.
+ *
+ * If x is null, this returns null.
+ */
+Dygraph.prototype.toDataXCoord = function(x) {
+  if (x == null) {
+    return null;
+  }
+
   var area = this.plotter_.area;
-  if (x !== null) {
-    var xRange = this.xAxisRange();
-    ret[0] = xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
+  var xRange = this.xAxisRange();
+  return xRange[0] + (x - area.x) / area.w * (xRange[1] - xRange[0]);
+};
+
+/**
+ * Convert from canvas/div y coord to value.
+ *
+ * If y is null, this returns null.
+ * if axis is null, this uses the first axis.
+ */
+Dygraph.prototype.toDataYCoord = function(y, axis) {
+  if (y == null) {
+    return null;
   }
 
-  if (y !== null) {
-    var yRange = this.yAxisRange(axis);
-    ret[1] = yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]);
+  var area = this.plotter_.area;
+  var yRange = this.yAxisRange(axis);
+
+  if (!this.attr_("logscale")) {
+    return yRange[0] + (area.h - y) / area.h * (yRange[1] - yRange[0]);
+  } else {
+    // Computing the inverse of toDomCoord.
+    var pct = (y - area.y) / area.h
+
+    // Computing the inverse of toPercentYCoord. The function was arrived at with
+    // the following steps:
+    //
+    // Original calcuation:
+    // pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
+    //
+    // Move denominator to both sides:
+    // pct * (logr1 - Dygraph.log10(yRange[0])) = logr1 - Dygraph.log10(y);
+    //
+    // subtract logr1, and take the negative value.
+    // logr1 - (pct * (logr1 - Dygraph.log10(yRange[0]))) = Dygraph.log10(y);
+    //
+    // Swap both sides of the equation, and we can compute the log of the
+    // return value. Which means we just need to use that as the exponent in
+    // e^exponent.
+    // Dygraph.log10(y) = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
+
+    var logr1 = Dygraph.log10(yRange[1]);
+    var exponent = logr1 - (pct * (logr1 - Dygraph.log10(yRange[0])));
+    var value = Math.pow(Dygraph.LOG_SCALE, exponent);
+    return value;
+  }
+};
+
+/**
+ * Converts a y for an axis to a percentage from the top to the
+ * bottom of the div.
+ *
+ * If the coordinate represents a value visible on the canvas, then
+ * the value will be between 0 and 1, where 0 is the top of the canvas.
+ * However, this method will return values outside the range, as
+ * values can fall outside the canvas.
+ *
+ * If y is null, this returns null.
+ * if axis is null, this uses the first axis.
+ */
+Dygraph.prototype.toPercentYCoord = function(y, axis) {
+  if (y == null) {
+    return null;
   }
 
-  return ret;
-};
+  var area = this.plotter_.area;
+  var yRange = this.yAxisRange(axis);
+
+  var pct;
+  if (!this.attr_("logscale")) {
+    // yrange[1] - y is unit distance from the bottom.
+    // yrange[1] - yrange[0] is the scale of the range.
+    // (yRange[1] - y) / (yRange[1] - yRange[0]) is the % from the bottom.
+    pct = (yRange[1] - y) / (yRange[1] - yRange[0]);
+  } else {
+    var logr1 = Dygraph.log10(yRange[1]);
+    pct = (logr1 - Dygraph.log10(y)) / (logr1 - Dygraph.log10(yRange[0]));
+  }
+  return pct;
+}
 
 /**
  * Returns the number of columns (including the independent variable).
@@ -798,19 +908,6 @@ Dygraph.prototype.dragGetY_ = function(e, context) {
 // panning behavior.
 //
 Dygraph.startPan = function(event, g, context) {
-  // have to be zoomed in to pan.
-  // TODO(konigsberg): Let's loosen this zoom-to-pan restriction, also
-  // perhaps create panning boundaries? A more flexible pan would make it,
-  // ahem, 'pan-useful'.
-  var zoomedY = false;
-  for (var i = 0; i < g.axes_.length; i++) {
-    if (g.axes_[i].valueWindow || g.axes_[i].valueRange) {
-      zoomedY = true;
-      break;
-    }
-  }
-  if (!g.dateWindow_ && !zoomedY) return;
-
   context.isPanning = true;
   var xRange = g.xAxisRange();
   context.dateRange = xRange[1] - xRange[0];
@@ -822,8 +919,7 @@ Dygraph.startPan = function(event, g, context) {
     var axis = g.axes_[i];
     var yRange = g.yAxisRange(i);
     axis.dragValueRange = yRange[1] - yRange[0];
-    var r = g.toDataCoords(null, context.dragStartY, i);
-    axis.draggingValue = r[1];
+    axis.draggingValue = g.toDataYCoord(context.dragStartY, i);
     if (axis.valueWindow || axis.valueRange) context.is2DPan = true;
   }
 
@@ -857,11 +953,18 @@ Dygraph.movePan = function(event, g, context) {
   // y-axis scaling is automatic unless this is a full 2D pan.
   if (context.is2DPan) {
     // Adjust each axis appropriately.
+    // NOTE(konigsberg): I don't think this computation for y_frac is correct.
+    // I think it doesn't take into account the display of the x axis.
+    // See, when I tested this with console.log(y_frac), and move the mouse
+    // cursor to the botom, the largest y_frac was 0.94, and not 1.0. That
+    // could also explain why panning tends to start with a small jumpy shift.
     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 minValue = maxValue - axis.dragValueRange;
+      console.log(axis.draggingValue, axis.dragValueRange, minValue, maxValue, y_frac);
       axis.valueWindow = [ minValue, maxValue ];
     }
   }
@@ -987,50 +1090,50 @@ Dygraph.endZoom = function(event, g, context) {
 
 Dygraph.defaultInteractionModel = {
   // Track the beginning of drag events
-  mousedown : function(event, g, context) {
-      context.initializeMouseDown(event, g, context);
+  mousedown: function(event, g, context) {
+    context.initializeMouseDown(event, g, context);
 
-      if (event.altKey || event.shiftKey) {
-        Dygraph.startPan(event, g, context);
-      } else {
-        Dygraph.startZoom(event, g, context);
-      }
-    },
+    if (event.altKey || event.shiftKey) {
+      Dygraph.startPan(event, g, context);
+    } else {
+      Dygraph.startZoom(event, g, context);
+    }
+  },
 
   // Draw zoom rectangles when the mouse is down and the user moves around
-  mousemove : function(event, g, context) {
-      if (context.isZooming) {
-        Dygraph.moveZoom(event, g, context);
-      } else if (context.isPanning) {
-        Dygraph.movePan(event, g, context);
-      }
-    },
+  mousemove: function(event, g, context) {
+    if (context.isZooming) {
+      Dygraph.moveZoom(event, g, context);
+    } else if (context.isPanning) {
+      Dygraph.movePan(event, g, context);
+    }
+  },
 
-  mouseup : function(event, g, context) {
-      if (context.isZooming) {
-        Dygraph.endZoom(event, g, context);
-      } else if (context.isPanning) {
-        Dygraph.endPan(event, g, context);
-      }
-    },
+  mouseup: function(event, g, context) {
+    if (context.isZooming) {
+      Dygraph.endZoom(event, g, context);
+    } else if (context.isPanning) {
+      Dygraph.endPan(event, g, context);
+    }
+  },
 
   // Temporarily cancel the dragging event when the mouse leaves the graph
-  mouseout : function(event, g, context) {
-      if (context.isZooming) {
-        context.dragEndX = null;
-        context.dragEndY = null;
-      }
-    },
+  mouseout: function(event, g, context) {
+    if (context.isZooming) {
+      context.dragEndX = null;
+      context.dragEndY = null;
+    }
+  },
 
   // Disable zooming out if panning.
-  dblclick : function(event, g, context) {
-      if (event.altKey || event.shiftKey) {
-        return;
-      }
-      // TODO(konigsberg): replace g.doUnzoom()_ with something that is
-      // friendlier to public use.
-      g.doUnzoom_();
+  dblclick: function(event, g, context) {
+    if (event.altKey || event.shiftKey) {
+      return;
     }
+    // TODO(konigsberg): replace g.doUnzoom()_ with something that is
+    // friendlier to public use.
+    g.doUnzoom_();
+  }
 };
 
 Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.defaultInteractionModel;
@@ -1043,43 +1146,43 @@ Dygraph.DEFAULT_ATTRS.interactionModel = Dygraph.defaultInteractionModel;
 Dygraph.prototype.createDragInterface_ = function() {
   var context = {
     // Tracks whether the mouse is down right now
-    isZooming : false,
-    isPanning : false,  // is this drag part of a pan?
-    is2DPan : false,    // if so, is that pan 1- or 2-dimensional?
-    dragStartX : null,
-    dragStartY : null,
-    dragEndX : null,
-    dragEndY : null,
-    dragDirection : null,
-    prevEndX : null,
-    prevEndY : null,
-    prevDragDirection : null,
+    isZooming: false,
+    isPanning: false,  // is this drag part of a pan?
+    is2DPan: false,    // if so, is that pan 1- or 2-dimensional?
+    dragStartX: null,
+    dragStartY: null,
+    dragEndX: null,
+    dragEndY: null,
+    dragDirection: null,
+    prevEndX: null,
+    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,
+    draggingDate: null,
 
     // TODO(danvk): update this comment
     // The range in second/value units that the viewport encompasses during a
     // panning operation.
-    dateRange : null,
+    dateRange: null,
 
     // Utility function to convert page-wide coordinates to canvas coords
-    px : 0,
-    py : 0,
+    px: 0,
+    py: 0,
 
-    initializeMouseDown : function(event, g, context) {
+    initializeMouseDown: function(event, g, context) {
       // prevents mouse drags from selecting page text.
       if (event.preventDefault) {
         event.preventDefault();  // Firefox, Chrome, etc.
       } else {
         event.returnValue = false;  // IE
-        event.cancelBubble = true;  
+        event.cancelBubble = true;
       }
-  
+
       context.px = Dygraph.findPosX(g.canvas_);
       context.py = Dygraph.findPosY(g.canvas_);
       context.dragStartX = g.dragGetX_(event, context);
@@ -1089,7 +1192,6 @@ Dygraph.prototype.createDragInterface_ = function() {
 
   var interactionModel = this.attr_("interactionModel");
 
-
   // Self is the graph.
   var self = this;
 
@@ -1192,10 +1294,8 @@ Dygraph.prototype.drawZoomRect_ = function(direction, startX, endX, startY, endY
 Dygraph.prototype.doZoomX_ = function(lowX, highX) {
   // Find the earliest and latest dates contained in this canvasx range.
   // Convert the call to date ranges of the raw data.
-  var r = this.toDataCoords(lowX, null);
-  var minDate = r[0];
-  r = this.toDataCoords(highX, null);
-  var maxDate = r[0];
+  var minDate = this.toDataXCoord(lowX);
+  var maxDate = this.toDataXCoord(highX);
   this.doZoomXDates_(minDate, maxDate);
 };
 
@@ -1231,10 +1331,10 @@ Dygraph.prototype.doZoomY_ = function(lowY, highY) {
   // coordinates increase as you go up the screen.
   var valueRanges = [];
   for (var i = 0; i < this.axes_.length; i++) {
-    var hi = this.toDataCoords(null, lowY, i);
-    var low = this.toDataCoords(null, highY, i);
-    this.axes_[i].valueWindow = [low[1], hi[1]];
-    valueRanges.push([low[1], hi[1]]);
+    var hi = this.toDataYCoord(lowY, i);
+    var low = this.toDataYCoord(highY, i);
+    this.axes_[i].valueWindow = [low, hi];
+    valueRanges.push([low, hi]);
   }
 
   this.drawGraph_();
@@ -1551,7 +1651,9 @@ Dygraph.hmsString_ = function(date) {
  * @private
  */
 Dygraph.dateAxisFormatter = function(date, granularity) {
-  if (granularity >= Dygraph.MONTHLY) {
+  if (granularity >= Dygraph.DECADAL) {
+    return date.strftime('%Y');
+  } else if (granularity >= Dygraph.MONTHLY) {
     return date.strftime('%b %y');
   } else {
     var frac = date.getHours() * 3600 + date.getMinutes() * 60 + date.getSeconds() + date.getMilliseconds();
@@ -1653,7 +1755,8 @@ Dygraph.QUARTERLY = 16;
 Dygraph.BIANNUAL = 17;
 Dygraph.ANNUAL = 18;
 Dygraph.DECADAL = 19;
-Dygraph.NUM_GRANULARITIES = 20;
+Dygraph.CENTENNIAL = 20;
+Dygraph.NUM_GRANULARITIES = 21;
 
 Dygraph.SHORT_SPACINGS = [];
 Dygraph.SHORT_SPACINGS[Dygraph.SECONDLY]        = 1000 * 1;
@@ -1689,6 +1792,7 @@ Dygraph.prototype.NumXTicks = function(start_time, end_time, granularity) {
     if (granularity == Dygraph.BIANNUAL) num_months = 2;
     if (granularity == Dygraph.ANNUAL) num_months = 1;
     if (granularity == Dygraph.DECADAL) { num_months = 1; year_mod = 10; }
+    if (granularity == Dygraph.CENTENNIAL) { num_months = 1; year_mod = 100; }
 
     var msInYear = 365.2524 * 24 * 3600 * 1000;
     var num_years = 1.0 * (end_time - start_time) / msInYear;
@@ -1761,6 +1865,11 @@ Dygraph.prototype.GetXAxis = function(start_time, end_time, granularity) {
     } else if (granularity == Dygraph.DECADAL) {
       months = [ 0 ];
       year_mod = 10;
+    } else if (granularity == Dygraph.CENTENNIAL) {
+      months = [ 0 ];
+      year_mod = 100;
+    } else {
+      this.warn("Span of dates is too long");
     }
 
     var start_year = new Date(start_time).getFullYear();
@@ -1807,6 +1916,8 @@ Dygraph.dateTicker = function(startDate, endDate, self) {
 
 /**
  * Add ticks when the x axis has numbers on it (instead of dates)
+ * TODO(konigsberg): Update comment.
+ *
  * @param {Number} startDate Start of the date window (millis since epoch)
  * @param {Number} endDate End of the date window (millis since epoch)
  * @param self
@@ -1826,43 +1937,61 @@ Dygraph.numericTicks = function(minV, maxV, self, axis_props, vals) {
       ticks.push({v: vals[i]});
     }
   } else {
-    // 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 pixelsPerYLabel is what we use.
-    // TODO(danvk): version that works on a log scale.
-    if (attr("labelsKMG2")) {
-      var mults = [1, 2, 4, 8];
+    if (self.attr_("logscale")) {
+      // As opposed to the other ways for computing ticks, we're just going
+      // for nearby values. There's no reasonable way to scale the values
+      // (unless we want to show strings like "log(" + x + ")") in which case
+      // x can be integer values.
+
+      // so compute height / pixelsPerTick and move on.
+      var pixelsPerTick = attr('pixelsPerYLabel');
+      var nTicks  = Math.floor(self.height_ / pixelsPerTick);
+      var vv = minV;
+
+      // Construct the set of ticks.
+      for (var i = 0; i < nTicks; i++) {
+        ticks.push( {v: vv} );
+        vv = vv * Dygraph.LOG_SCALE;
+      }
     } 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 = attr('pixelsPerYLabel');
-    for (var i = -10; i < 50; i++) {
+      // 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 pixelsPerYLabel is what we use.
+      // TODO(danvk): version that works on a log scale.
       if (attr("labelsKMG2")) {
-        var base_scale = Math.pow(16, i);
+        var mults = [1, 2, 4, 8];
       } else {
-        var base_scale = Math.pow(10, i);
+        var mults = [1, 2, 5];
       }
-      for (var j = 0; j < mults.length; j++) {
-        scale = base_scale * mults[j];
-        low_val = Math.floor(minV / scale) * scale;
-        high_val = Math.ceil(maxV / scale) * scale;
-        nTicks = Math.abs(high_val - low_val) / scale;
-        var spacing = self.height_ / nTicks;
-        // wish I could break out of both loops at once...
+      var scale, low_val, high_val, nTicks;
+      // TODO(danvk): make it possible to set this for x- and y-axes independently.
+      var pixelsPerTick = attr('pixelsPerYLabel');
+      for (var i = -10; i < 50; i++) {
+        if (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;
+          high_val = Math.ceil(maxV / scale) * scale;
+          nTicks = Math.abs(high_val - low_val) / scale;
+          var spacing = self.height_ / nTicks;
+          // wish I could break out of both loops at once...
+          if (spacing > pixelsPerTick) break;
+        }
         if (spacing > pixelsPerTick) break;
       }
-      if (spacing > pixelsPerTick) break;
-    }
 
-    // Construct the set of ticks.
-    // Allow reverse y-axis if it's explicitly requested.
-    if (low_val > high_val) scale *= -1;
-    for (var i = 0; i < nTicks; i++) {
-      var tickV = low_val + i * scale;
-      ticks.push( {v: tickV} );
+      // Construct the set of ticks.
+      // Allow reverse y-axis if it's explicitly requested.
+      if (low_val > high_val) scale *= -1;
+      for (var i = 0; i < nTicks; i++) {
+        var tickV = low_val + i * scale;
+        ticks.push( {v: tickV} );
+      }
     }
   }
 
@@ -1975,7 +2104,6 @@ Dygraph.prototype.predraw_ = function() {
 };
 
 /**
-=======
  * Update the graph with new data. This method is called when the viewing area
  * has changed. If the underlying data or options have changed, predraw_ will
  * be called before drawGraph_ is called.
@@ -2231,6 +2359,7 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
 
   // Compute extreme values, a span and tick marks for each axis.
   for (var i = 0; i < this.axes_.length; i++) {
+    var isLogScale = this.attr_("logscale");
     var axis = this.axes_[i];
     if (axis.valueWindow) {
       // This is only set if the user has zoomed on the y-axis. It is never set
@@ -2255,18 +2384,26 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
       var span = maxY - minY;
       // special case: if we have no sense of scale, use +/-10% of the sole value.
       if (span == 0) { span = maxY; }
-      var maxAxisY = maxY + 0.1 * span;
-      var minAxisY = minY - 0.1 * span;
 
-      // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense.
-      if (!this.attr_("avoidMinZero")) {
-        if (minAxisY < 0 && minY >= 0) minAxisY = 0;
-        if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0;
-      }
+      var maxAxisY;
+      var minAxisY;
+      if (isLogScale) {
+        var maxAxisY = maxY + 0.1 * span;
+        var minAxisY = minY;
+      } else {
+        var maxAxisY = maxY + 0.1 * span;
+        var minAxisY = minY - 0.1 * span;
 
-      if (this.attr_("includeZero")) {
-        if (maxY < 0) maxAxisY = 0;
-        if (minY > 0) minAxisY = 0;
+        // Try to include zero and make it minAxisY (or maxAxisY) if it makes sense.
+        if (!this.attr_("avoidMinZero")) {
+          if (minAxisY < 0 && minY >= 0) minAxisY = 0;
+          if (maxAxisY > 0 && maxY <= 0) maxAxisY = 0;
+        }
+
+        if (this.attr_("includeZero")) {
+          if (maxY < 0) maxAxisY = 0;
+          if (minY > 0) minAxisY = 0;
+        }
       }
 
       axis.computedValueRange = [minAxisY, maxAxisY];