/**
 * Axis component.
 * @param {DvtContext} context The rendering context.
 * @param {string} callback The function that should be called to dispatch component events.
 * @param {object} callbackObj The optional object instance on which the callback function is defined.
 * @param {object} options The object containing options specifications for this component.
 * @class
 * @constructor
 * @extends {DvtContainer}
 */
var DvtAxis = function(context, callback, callbackObj, options) {
  this.Init(context, callback, callbackObj, options);
}

DvtObj.createSubclass(DvtAxis, DvtContainer, "DvtAxis");

/**
 * Initializes the component.
 * @param {DvtContext} context The rendering context.
 * @param {string} callback The function that should be called to dispatch component events.
 * @param {object} callbackObj The optional object instance on which the callback function is defined.
 * @param {object} options The object containing options specifications for this component.
 * @protected
 */
DvtAxis.prototype.Init = function(context, callback, callbackObj, options) {
  // Note: The callback is not used for now, but we ask for it for extensibility reasons
  DvtAxis.superclass.Init.call(this, context);
  this.setOptions(options);
  
  // Create the event handler and add event listeners
  this._eventHandler = new DvtEventManager(context);
  this._eventHandler.addListeners(this);
}

/**
 * Specifies the non-data options for this component.
 * @param {object} options The object containing options specifications for this component.
 * @protected
 */
DvtAxis.prototype.setOptions = function(options) {
  // Combine the user options with the defaults and store
  this.Options = DvtAxisDefaults.calcOptions(options);
}

/**
 * Returns the preferred dimensions for this component given the maximum available space.
 * @param {object} data The object containing data for this component.
 * @param {Number} maxWidth The maximum width available.
 * @param {Number} maxHeight The maximum height available.
 * @return {object} The preferred dimensions for the object.
 */
DvtAxis.prototype.getPreferredSize = function(data, maxWidth, maxHeight) {
  // Set the layout flag to indicate this is a layout pass only
  this.Options.isLayout = true;

  // Ask the axis to render its context in the max space and find the space used
  this.render(data, maxWidth, maxHeight);
  var dims = this.getDimensions();
  var width = dims.w;
  var height = dims.h;
  
  // Clear the rendered contents and reset state
  this.Options.isLayout = false;
  this.removeChildren();
  
  // Return the height needed.  Reserve the full space along the edge of the axis.
  var position = this.Options.position;
  if(position == "top" || position == "bottom") {
    return {width: maxWidth, height: Math.min(dims.h, maxHeight)};
  } else {
    // In Chrome, when getDimensioins is called on DvtAxis the width will sometimes be less the widest text element
    // that is a child of it. When we actually render text labels can get dropped or truncated incorrectly. Work around 
    // is to add 10% to the width calculated. To see this, do not add 10% to the dims.w and render a default bar graph
    // in Chrome 13.0.782.215
    var extraWidth = Math.max(1, dims.w * .1);
    return {width: Math.min(dims.w + extraWidth, maxWidth), height: maxHeight};
  }
}

/**
 * Renders the component at the specified size.
 * @param {object} data The object containing data for this component.
 * @param {number} width The width of the component.
 * @param {number} height The height of the component.
 */
DvtAxis.prototype.render = function(data, width, height) 
{  
  this.Width = width;
  this.Height = height;
  
  // Store the data object. Clone to avoid modifying the provided object.
  this.Data = DvtJSONUtils.clone(data);
  
  // Clear any contents rendered previously
  this.removeChildren();
  
  // Render the axis
  var availSpace = new DvtRectangle(0, 0, width, height);
  DvtAxisRenderer.render(this, availSpace);
  
  // Queue a render with the context
  this.getContext().queueRender();
}

/**
 * Returns the data object for the component.
 * @return {object} The object containing data for this component.
 */
DvtAxis.prototype.__getData = function() {
  return this.Data ? this.Data : {};
}

/**
 * Returns the evaluated options object, which contains the user specifications
 * merged with the defaults.
 * @return {object} The options object.
 */
DvtAxis.prototype.__getOptions = function() {
  return this.Options;
}

/**
 * Returns the DvtEventManager for this component.
 * @return {DvtEventManager}
 */
DvtAxis.prototype.__getEventManager = function() {
  return this._eventHandler;
}

/**
 * Returns the axisInfo for the axis
 * @return {DvtAxisInfo} the axisInfo
 */
 DvtAxis.prototype.__getInfo = function() {
   return this.Info;
 }

/**
 * Sets the object containing calculated axis information and support
 * for creating drawables.
 * @param {DvtAxisInfo} axisInfo
 */
DvtAxis.prototype.__setInfo = function(axisInfo) {
  this.Info = axisInfo;
}
/**
 * Default values and utility functions for axis versioning.
 * @class
 */
var DvtAxisDefaults = new Object();

DvtObj.createSubclass(DvtAxisDefaults, DvtObj, "DvtAxisDefaults");

/**
 * Defaults for version 1.
 */ 
DvtAxisDefaults.VERSION_1 = {
  position: null,
  minValue: null, maxValue: null, majorIncrement: null, minorIncrement: null,
  scaledFromBaseline: "on",
  timeRangeMode: "explicit",
  titleStyle: "font-size: 12px; color: #74779A;", 
  tickLabel: {style: "font-size: 11px; color: #404259;", rendered: true},
  majorTick: {lineColor: "rgba(138,141,172,0.4)", lineWidth: 1, rendered: null},
  minorTick: {lineColor: "rgba(138,141,172,0.20)", lineWidth: 1, rendered: false},
  axisLine: {lineColor: "#8A8DAC", lineWidth: 1, rendered: true},
  
  // For group axis, an optional offset expressed as a factor of the group size.
  startGroupOffset: 0, endGroupOffset: 0,
  
  //*********** Internal Attributes *************************************************//
  layout: {
    // Gap ratio is multiplied against all gap sizes
    gapRatio: 1.0,
    titleGap: 9,
    verticalAxisGap: 2,
    horizontalAxisGap: 2
  },
  
  isLayout: false // true if rendering for layout purposes
};

/**
 * Combines the user options with the defaults for the specified version.  Returns the
 * combined options object.  This object will contain internal attribute values and
 * should be accessed in internal code only.
 * @param {object} userOptions The object containing options specifications for this component.
 * @return {object} The combined options object.
 */
DvtAxisDefaults.calcOptions = function(userOptions) {
  var defaults = DvtAxisDefaults._getDefaults(userOptions);
  
  // Use defaults if no overrides specified
  if(!userOptions)
    return defaults;
  else // Merge the options object with the defaults
    return DvtJSONUtils.merge(userOptions, defaults);
}

/**
 * Returns the default options object for the specified version of the component.
 * @param {object} userOptions The object containing options specifications for this component.
 * @private
 */
DvtAxisDefaults._getDefaults = function(userOptions) {
  // Note: Version checking will eventually get added here
  // Note: Future defaults objects are deltas on top of previous objects
  return DvtJSONUtils.clone(DvtAxisDefaults.VERSION_1);
}

/**
 * Scales down gap sizes based on the size of the component.
 * @param {object} options The object containing options specifications for this component.
 * @param {Number} defaultSize The default gap size.
 * @return {Number} 
 */
DvtAxisDefaults.getGapSize = function(options, defaultSize) {
  return Math.ceil(defaultSize * options.layout.gapRatio);
}
/**
 * Renderer for DvtAxis.
 * @class
 */
var DvtAxisRenderer = new Object();

DvtObj.createSubclass(DvtAxisRenderer, DvtObj, "DvtAxisRenderer");

/**
 * Renders the axis and updates the available space.
 * @param {DvtAxis} axis The axis being rendered.
 * @param {DvtRectangle} availSpace The available space.
 */
DvtAxisRenderer.render = function(axis, availSpace) {
  // Calculate the axis extents and increments
  var axisInfo = DvtAxisInfo.newInstance(axis.__getData(), axis.__getOptions(), availSpace);
  axis.__setInfo(axisInfo);
  
  // Render the title
  DvtAxisRenderer._renderTitle(axis, axisInfo, availSpace);
  
  // Render the tick labels
  DvtAxisRenderer._renderLabels(axis, axisInfo, availSpace);
}

/**
 * Renders the axis title and updates the available space.
 * @param {DvtAxis} axis The axis being rendered.
 * @param {DvtAxisInfo} axisInfo The axis model.
 * @param {DvtRectangle} availSpace The available space.
 * @private
 */
DvtAxisRenderer._renderTitle = function(axis, axisInfo, availSpace) {
  var data = axis.__getData();
  var options = axis.__getOptions();  
  
  if(!data.title)
    return;
    
  // Create the title object and add to axis
  var position = options.position;
  
  // In layout mode, treat "bottom" as "top" for space calculation
  if(options.isLayout && position == "bottom")
    position = "top";
    
  var title;
  if (position == "top" || position == "bottom")
    title = DvtChartTextUtils.createText(axis.__getEventManager(), axis, data.title, options.titleStyle, 0, 0, availSpace.w, availSpace.h);
  else 
    title = DvtChartTextUtils.createText(axis.__getEventManager(), axis, data.title, options.titleStyle, 0, 0, availSpace.h, availSpace.w);
 
  if (title) {
    // Position the title based on text size and axis position 
    var titleDims = title.getDimensions();
    var gap = DvtAxisDefaults.getGapSize(options, options.layout.titleGap);
    
    // Position the label and update the space
    if(position == "top") {
      title.setX(availSpace.x + availSpace.w/2 - titleDims.w/2); 
      title.setY(availSpace.y);
      availSpace.y += (titleDims.h + gap);
      availSpace.h -= (titleDims.h + gap);
    }
    else if(position == "bottom") {
      title.setX(availSpace.x + availSpace.w/2 - titleDims.w/2); 
      title.setY(availSpace.y + availSpace.h - titleDims.h);
      availSpace.h -= (titleDims.h + gap);
    }
    else if(position == "left") {
      title.setRotation(3*Math.PI/2);
      title.setTranslateX(availSpace.x);
      title.setTranslateY(availSpace.y + availSpace.h/2 + titleDims.w/2);
      availSpace.x += (titleDims.h + gap);
      availSpace.w -= (titleDims.h + gap);
    }
    else if(position == "right") {
      title.setRotation(Math.PI/2);
      title.setTranslateX(availSpace.x + availSpace.w);
      title.setTranslateY(availSpace.y + availSpace.h/2 - titleDims.w/2);
      availSpace.w -= (titleDims.h + gap);
    }
    DvtLayoutUtils.alignTextStart(title);
  }
}

/**
 * Renders the tick labels and updates the available space.
 * @param {DvtAxis} axis The axis being rendered.
 * @param {DvtAxisInfo} axisInfo The axis model.
 * @param {DvtRectangle} availSpace The available space.
 * @private
 */
DvtAxisRenderer._renderLabels = function(axis, axisInfo, availSpace) {
  var options = axis.__getOptions();
  if(options.tickLabel.rendered) {
    // Axis labels are positioned based on the position of the axis.  In layout
    // mode, the labels will be positioned as close to the title as possible to
    // calculate the actual space used.
    var position = options.position;
    if(position == "top" || position == "bottom") 
      DvtAxisRenderer._renderLabelsHoriz(axis, axisInfo, availSpace);
    else 
      DvtAxisRenderer._renderLabelsVert(axis, axisInfo, availSpace);
  }
}

/**
 * Renders tick labels for a horizontal axis and updates the available space.
 * @param {DvtAxis} axis The axis being rendered.
 * @param {DvtAxisInfo} axisInfo The axis model.
 * @param {DvtRectangle} availSpace The available space.
 * @private
 */
DvtAxisRenderer._renderLabelsHoriz = function(axis, axisInfo, availSpace) {
  // Position and add the axis labels.  
  var labels = axisInfo.getLabels(axis.getContext());
  for(var i=0; i<labels.length; i++) {
    var label = labels[i];
    label.alignTop();
    label.setY(availSpace.y);
    axis.addChild(label);
  }
}

/**
 * Renders tick labels for a vertical axis and updates the available space.
 * @param {DvtAxis} axis The axis being rendered.
 * @param {DvtAxisInfo} axisInfo The axis model.
 * @param {DvtRectangle} availSpace The available space.
 * @private
 */
DvtAxisRenderer._renderLabelsVert = function(axis, axisInfo, availSpace) {
  var options = axis.__getOptions();
  var isLayout = options.isLayout;
  var position = options.position;
  
  // All vertical axis labels are aligned to the end, unless in layout mode
  var labelX = availSpace.x + availSpace.w;
  if(isLayout && position == "left")
    labelX = availSpace.x;
  
  // Position and add the axis labels.  
  var labels = axisInfo.getLabels(axis.getContext());
  for(var i=0; i<labels.length; i++) {
    var label = labels[i];
    var textBefore = label.getTextString();
    label = (isLayout ? label : label.truncateToSpace(axis, availSpace.w, availSpace.h));
    if (!label)
      continue;
    var textAfter = label.getTextString();
    if (textAfter.length < textBefore.length)
      axis.__getEventManager().associate(label, new DvtSimpleObjPeer(textBefore));
    label.setX(labelX);
    DvtLayoutUtils.alignTextEnd(label);
    axis.addChild(label);
    
    if(isLayout && position == "left")
      label.alignStart();
  }
}

/**
 * Calculated axis information and drawable creation.  This class should
 * not be instantiated directly.
 * @class
 * @constructor
 * @extends {DvtObj}
 */
var DvtAxisInfo = function() {}

DvtObj.createSubclass(DvtAxisInfo, DvtObj, "DvtAxisInfo");

/**
 * Creates an appropriate instance of DvtAxisInfo with the specified parameters.
 * @param {object} data The object containing data for this component.
 * @param {object} options The object containing options specifications for this component.
 * @param {DvtRectangle} availSpace The available space.
 * @return {DvtAxisInfo}
 */
DvtAxisInfo.newInstance = function(data, options, availSpace) {
  if(data.timeAxisType != "disabled")
    return new DvtTimeAxisInfo(data, options, availSpace);
  else if(isNaN(data.minDataValue) && isNaN(data.maxDataValue))
    return new DvtGroupAxisInfo(data, options, availSpace);
  else
    return new DvtDataAxisInfo(data, options, availSpace);
}

/**
 * Calculates and stores the axis information.
 * @param {object} data The object containing data for this component.
 * @param {object} options The object containing options specifications for this component.
 * @param {DvtRectangle} availSpace The available space.
 * @protected
 */
DvtAxisInfo.prototype.Init = function(data, options, availSpace) {
  // Figure out the start and end coordinate of the axis
  this.Position = options.position;
  if(this.Position == "top" || this.Position == "bottom") {
    this.StartCoord = availSpace.x;
    this.EndCoord = availSpace.x + availSpace.w;
  }
  else {
    this.StartCoord = availSpace.y;
    this.EndCoord = availSpace.y + availSpace.h;
  }
  
  // Store the data and options
  this.Data = data;
  
  this.Options = options;
}

/**
 * Returns the options settings for the axis.
 * @return {object} The options for the axis.
 */
DvtAxisInfo.prototype.getOptions = function() {
  return this.Options;
}

/**
 * Returns an array containing the tick labels for this axis.
 * @param {DvtContext} context
 * @return {Array} The Array of DvtText objects.
 */
DvtAxisInfo.prototype.getLabels = function(context) {
  return []; // subclasses should override
}

/**
 * Returns the axis line for this axis.
 * @param {DvtContext} context
 * @return {DvtLine} The axis line.
 */
DvtAxisInfo.prototype.getAxisLine = function(context) {
  return null; // subclasses should override
}

/**
 * Returns an array containing the grid lines for this axis.  Objects
 * are returned in the desired z-order.
 * @param {DvtContext} context
 * @return {Array} The Array of DvtLine objects.
 */
DvtAxisInfo.prototype.getGridLines = function(context) {
  return []; // subclasses should override
}

/**
 * Returns the value for the specified coordinate along the axis.  Returns null
 * if the coordinate is not within the axis.
 * @param {number} coord The coordinate along the axis.
 * @return {object} The value at that coordinate.
 */
DvtAxisInfo.prototype.getValueAt = function(coord) {
  return null; // subclasses should override
}

/**
 * Returns the coordinate for the specified value.  Returns null if the value is
 * not within the axis.
 * @param {object} value The value to locate.
 * @return {number} The coordinate for the value.
 */
DvtAxisInfo.prototype.getCoordAt = function(value) {
  return null; // subclasses should override
}

/**
 * Returns the value for the specified coordinate along the axis.  If a coordinate
 * is not within the axis, returns the value of the closest coordinate within the axis.
 * @param {number} coord The coordinate along the axis.
 * @return {object} The value at that coordinate.
 */
DvtAxisInfo.prototype.getBoundedValueAt = function(coord) {
  return null; // subclasses should override
}

/**
 * Returns the coordinate for the specified value along the axis.  If a value
 * is not within the axis, returns the coordinate of the closest value within the axis.
 * @param {object} value The value to locate.
 * @return {number} The coordinate for the value.
 */
DvtAxisInfo.prototype.getBoundedCoordAt = function(value) {
  return null; // subclasses should override
}

/**
 * Returns the coordinate for the specified value.
 * @param {object} value The value to locate.
 * @return {number} The coordinate for the value.
 */
DvtAxisInfo.prototype.getUnboundedCoordAt = function(value) {
  return null; // subclasses should override
}

/**
 * Returns the baseline coordinate for the axis, if applicable.
 * @return {number} The baseline coordinate for the axis.
 */
DvtAxisInfo.prototype.getBaselineCoord = function() {
  return null;
}

/**
 * Creates a DvtText instance for the specified text label.
 * @param {DvtContext} context
 * @param {string} label The label string.
 * @param {number} coord The coordinate for the text.
 * @return {DvtText} 
 * @protected
 */
DvtAxisInfo.prototype.CreateLabel = function(context, label, coord) {
  var text = new DvtText(context, label, coord, coord);
  text.setCSSStyle(new DvtCSSStyle(this.Options.tickLabel.style));
  text.alignCenter();
  text.alignMiddle();
  return text;
}

/**
 * Creates a DvtLine instance with the specified stroke and coordinate.
 * @param {DvtContext} context
 * @param {DvtSolidStroke} stroke The stroke for the grid line.
 * @param {number} coord The coordinate for the grid line.
 * @return {DvtText} 
 * @protected
 */
DvtAxisInfo.prototype.CreateGridLine = function(context, stroke, coord) {
  var line = new DvtLine(context, coord, coord, coord, coord);
  line.setStroke(stroke);
  line.setPixelHinting(true);
  line.setMouseEnabled(false);
  return line;
}

/**
 * Checks all the labels for the axis and returns whether they overlap.
 * @param {Array} labelDims An array of DvtRectangle objects that describe the x, y, height, width of the axis labels.
 * @param {number} skippedLabels The number of labels to skip. If skippedLabels is 1 then every other label will be skipped.
 * @return {boolean} True if any labels overlap.
 * @private
 */
DvtAxisInfo.prototype._isOverlapping = function(labelDims, skippedLabels) {
  // If there are no labels, return
  if(!labelDims || labelDims.length <= 0)
    return false;

  var isVert = (this.Position == "left" || this.Position == "right");
  var gap = (isVert ? this.Options.layout.verticalAxisGap : this.Options.layout.horizontalAxisGap);
  var isBIDI = DvtStyleUtils.isLocaleR2L();
  
  var pointA1, pointA2, pointB1, pointB2;
  if (isVert) {
    pointA1 = labelDims[0].y;
    pointA2 = labelDims[0].y + labelDims[0].h;
  } else {
    pointA1 = labelDims[0].x;
    pointA2 = labelDims[0].x + labelDims[0].w;
  }

  for (var j=skippedLabels+1; j<labelDims.length; j+= skippedLabels+1) {
    if (isVert) {
      pointB1 = labelDims[j].y;
      pointB2 = labelDims[j].y + labelDims[j].h;
      
      // Broken apart for clarity, next label may be above or below
      if(pointB1 >= pointA1 && pointB1 - gap < pointA2) // next label below
        return true;
      else if(pointB1 < pointA1 && pointB2 + gap > pointA1) // next label above
        return true;
    } 
    else {
      pointB1 = labelDims[j].x;
      pointB2 = labelDims[j].x + labelDims[j].w;
      
      // Broken apart for clarity, next label is on the right for non-BIDI, left for BIDI
      if(!isBIDI && (pointB1 - gap < pointA2))
        return true;
      else if(isBIDI && (pointB2 + gap > pointA1))
        return true;
    }
    
    // Otherwise start evaluating from label j
    pointA1 = pointB1 + gap;
    pointA2 = pointB2 - gap;
  }
  return false;
}

/**
 * Checks the labels for the axis and skips them as necessary.
 * @param {Array} labels An array of DvtText labels for the axis.
 * @param {Array} labelDims An array of DvtRectangle objects that describe the x, y, height, width of the axis labels.
 * @return {Array} The array of DvtText labels for the axis.
 * @protected
 */
DvtAxisInfo.prototype.SkipLabels = function(labels, labelDims) {
  var skippedLabels = 0;
  var bOverlaps = this._isOverlapping(labelDims, skippedLabels);
  while (bOverlaps) {
    skippedLabels++;
    bOverlaps = this._isOverlapping(labelDims, skippedLabels);
  }
  
  if (skippedLabels > 0) {
    var renderedLabels = [];
    for (var j=0; j<labels.length; j+= skippedLabels+1) {
      renderedLabels.push(labels[j]);
    }
    return renderedLabels;
  } else {
    return labels
  }
}

/**
 * Returns the number of major tick counts for the axis.
 * @return {number} The number of major tick counts.
 */
DvtAxisInfo.prototype.getMajorTickCount = function() {
   return null; // subclasses that allow major gridlines should implement
}

/**
 * Sets the number of major tick counts for the axis.
 * @param {number} count The number of major tick counts.
 */
DvtAxisInfo.prototype.setMajorTickCount = function(count) {
  // subclasses that allow major gridlines should implement
}

/**
 * Returns the number of minor tick counts for the axis.
 * @return {number} The number of minor tick counts.
 */
DvtAxisInfo.prototype.getMinorTickCount = function() {
   return null; // subclasses that allow minor gridlines should implement
}

/**
 * Sets the number of minor tick counts for the axis.
 * @param {number} count The number of minor tick counts.
 */
DvtAxisInfo.prototype.setMinorTickCount = function(count) {
  // subclasses that allow minor gridlines should implement
}

/**
 * Returns the major increment for the axis.
 * @return {number} The major increment.
 */
DvtAxisInfo.prototype.getMajorIncrement = function() {
   return null; // subclasses that allow major gridlines should implement
}

/**
 * Returns the minor increment for the axis.
 * @return {number} The minor increment.
 */
DvtAxisInfo.prototype.getMinorIncrement = function() {
   return null; // subclasses that allow minor gridlines should implement
}

/**
 * Calculated axis information and drawable creation for a data axis.
 * @param {object} data The object containing data for this component.
 * @param {object} options The object containing options specifications for this component.
 * @param {DvtRectangle} availSpace The available space.
 * @class
 * @constructor
 * @extends {DvtAxisInfo}
 */
var DvtDataAxisInfo = function(data, options, availSpace) {
  this.Init(data, options, availSpace);
}

DvtObj.createSubclass(DvtDataAxisInfo, DvtAxisInfo, "DvtDataAxisInfo");

DvtDataAxisInfo._MAX_NUMBER_OF_GRIDS_AUTO = 10;
DvtDataAxisInfo._MINOR_TICK_COUNT = 2;

/**
 * @override
 */
DvtDataAxisInfo.prototype.Init = function(data, options, availSpace) {
  DvtDataAxisInfo.superclass.Init.call(this, data, options, availSpace);

  // Figure out the coords for the min/max values
  if(this.Position == "top" || this.Position == "bottom") {
    // Axis is horizontal, so flip for BIDI if needed
    if(DvtStyleUtils.isLocaleR2L()) {
      this._minCoord = this.EndCoord;
      this._maxCoord = this.StartCoord;
    }
    else {
      this._minCoord = this.StartCoord;
      this._maxCoord = this.EndCoord;
    }
  }
  else {
    this._minCoord = this.EndCoord;
    this._maxCoord = this.StartCoord;
  }

  this._minValue = options.minValue;
  this._maxValue = options.maxValue;
  this._majorIncrement = options.majorIncrement;
  this._minorIncrement = options.minorIncrement;
  this._converter = null;
  if (options.tickLabel !== undefined) {
    this._converter = options.tickLabel.converter;
  }
  this._calcAxisExtents(data.minDataValue, data.maxDataValue);
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getLabels = function(context) {
  var labels = [];
  var labelDims = [];
  var container = context.getStage();
  
  // Iterate on an integer to reduce rounding error.  We use <= since the first
  // tick is not counted in the tick count.
  for(var i=0; i<=this._majorTickCount; i++) {
    var value = i*this._majorIncrement + this._minValue;
    var label = this._formatValue(value);
    var coord = this.getCoordAt(value);
    var text = this.CreateLabel(context, label, coord);
    container.addChild(text);
    var dims = text.getDimensions();
    container.removeChild(text);
    labelDims.push(dims);
    labels.push(text);
  }
  return this.SkipLabels(labels, labelDims);
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getAxisLine = function(context) {
  var axisLineOptions = this.Options.axisLine;
  if(axisLineOptions.rendered) {
    // TODO hzhang Check Axis Line behavior for negative/mixed axes.
    var axisLineStroke = new DvtSolidStroke(axisLineOptions.lineColor, 1, axisLineOptions.lineWidth);
    return this.CreateGridLine(context, axisLineStroke, 10);
  }
  else 
    return null;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getGridLines = function(context) {
  var gridlines = [];
  
  // Major and Minor Ticks
  var coord, line;
  var majorTickOptions = this.Options.majorTick;
  var majorTickStroke = new DvtSolidStroke(majorTickOptions.lineColor, 1, majorTickOptions.lineWidth);
  var minorTickOptions = this.Options.minorTick;
  var minorTickStroke = new DvtSolidStroke(minorTickOptions.lineColor, 1, minorTickOptions.lineWidth);
  
  // Iterate on an integer to reduce rounding error.  We use <= since the first
  // tick is not counted in the tick count.
  for(var i=0; i<=this._majorTickCount; i++) {
    var value = i*this._majorIncrement + this._minValue;
    
    // Minor Ticks
    if(minorTickOptions.rendered) {
      for (var j=1; j<this._minorTickCount; j++) {
        var minorValue = value+(j*this._minorIncrement);
        if(minorValue > this._maxValue)
          break;
        
        coord = this.getCoordAt(minorValue);
        line = this.CreateGridLine(context, minorTickStroke, coord);
        gridlines.push(line);
      }  
    }
  
    // Major Ticks
    if(majorTickOptions.rendered !== false) {
      coord = this.getCoordAt(value);
      line = this.CreateGridLine(context, majorTickStroke, coord);
      gridlines.push(line);
    }
  }
  
  return gridlines;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getValueAt = function(coord) {
  // Return null if the coord is outside of the axis
  if(coord < this._minCoord || coord > this._maxCoord)
    return null;
  
  // Otherwise find the value
  var ratio = (coord - this._minCoord)/(this._maxCoord - this._minCoord);
  return this._minValue + (ratio * (this._maxValue - this._minValue));
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getCoordAt = function(value) {
  // Return null if the value is outside of the axis
  if(value < this._minValue || value > this._maxValue)
    return null;

  return this.getUnboundedCoordAt(value);
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getBoundedValueAt = function(coord) {
  if(coord < this._minCoord)
    coord = this._minCoord;
  else if(coord > this._maxCoord)
    coord = this._maxCoord;

  return this.getValueAt(coord);
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getBoundedCoordAt = function(value) {
  if(value < this._minValue)
    value = this._minValue;
  else if(value > this._maxValue)
    value = this._maxValue;

  return this.getUnboundedCoordAt(value);
}

// TODO impl getUnboundedValueAt

/**
 * @override
 */
DvtDataAxisInfo.prototype.getUnboundedCoordAt = function(value) {
  var ratio = (value - this._minValue)/(this._maxValue - this._minValue);
  return this._minCoord + (ratio * (this._maxCoord - this._minCoord));
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getBaselineCoord = function() {
  // First find the value of the baseline
  var baseline = 0;
  if(this._maxValue < 0)
    baseline = this._maxValue;
  else if(this._minValue > 0)
    baseline = this._minValue;
    
  // Return its coordinate
  return this.getCoordAt(baseline);
}

/**
 * @private
 */
DvtDataAxisInfo.prototype._formatValue = function(value) {

  if (this._converter && this._converter.getAsString && this._converter.getAsObject) {
    return this._converter.getAsString(value);
  } else {
    return Math.round(value);   
  }
}

/**
 * Determines the number of major and minor tick counts and increments for the axis if values were not given.
 * The default minor tick count is 2.
 * @param {number} scaleUnit The scale unit of the axis.
 * @private
 */
DvtDataAxisInfo.prototype._calcMajorMinorIncr = function(scaleUnit) {
  this._majorIncrement = this._majorIncrement ? this._majorIncrement : scaleUnit;
  this._majorTickCount = (this._maxValue - this._minValue)/this._majorIncrement;
  if (this._minorIncrement != null && this._majorIncrement/this._minorIncrement >= 2) {
    this._minorTickCount = this._majorIncrement/this._minorIncrement;
  } else {
    this._minorTickCount = DvtDataAxisInfo._MINOR_TICK_COUNT;
    this._minorIncrement = this._majorIncrement/this._minorTickCount;
  }
}

/**
 * Determines the axis extents based on given start and end value
 * or calculated from the min and max data values of the chart.
 * @param {number} minDataValue The minimum data value of the chart.
 * @param {number} maxDataValue The maximum data value of the chart.
 * @private
 */
DvtDataAxisInfo.prototype._calcAxisExtents = function(minDataValue, maxDataValue) {
  var maxValueToExtend = maxDataValue;
  
  // Include 0 in the axis if we're scaling from the baseline
  if(this.Options.scaledFromBaseline == "on") {
    minDataValue = Math.min(0, minDataValue);
    maxDataValue = Math.max(0, maxDataValue);
  }
  
  var scaleUnit = this._calcAxisScale((this._minValue !== null && !isNaN(this._minValue) ? this._minValue : minDataValue), 
                                      (this._maxValue !== null && !isNaN(this._maxValue) ? this._maxValue : maxDataValue));
                                      
  if(this._maxValue == null && this._minValue == null) {
    this._minValue = Math.floor(minDataValue/scaleUnit) * scaleUnit;
    while (this._minValue > minDataValue) 
      this._minValue -= scaleUnit;
    
    this._maxValue = this._minValue;
    while (this._maxValue <= maxValueToExtend || this._maxValue < 0)
      this._maxValue += scaleUnit;
  }
  else if (this._minValue == null) {
    this._minValue = this._maxValue;
    while (this._minValue > minDataValue) 
      this._minValue -= scaleUnit;
  } 
  else if (this._maxValue == null) {
    this._maxValue = this._minValue;
    while (this._maxValue <= maxValueToExtend || this._maxValue < 0)
      this._maxValue += scaleUnit;
  }
  
  if (isNaN(this._maxValue) && isNaN(this._minValue)) {
    this._maxValue = 100;
    this._minValue = 0;
    scaleUnit = (this._maxValue - this._minValue) / DvtDataAxisInfo._MAX_NUMBER_OF_GRIDS_AUTO;
  }
  
  // Calculate major and minor gridlines
  this._calcMajorMinorIncr(scaleUnit);
}

/**
 * Determines the scale unit of the axis based on a given start and end axis extent.
 * @param {number} min The start data value for the axis.
 * @param {number} max The end data value for the axis.
 * @return {number} The scale unit of the axis.
 * @private
 */
DvtDataAxisInfo.prototype._calcAxisScale = function(min, max) {
	var spread =  max - min;
	var t = Math.log(spread)/Math.log(10);
	var testVal = 0;
	
	if (t - Math.round(t) == 0)
		testVal = Math.pow(10,t - 2);
	else 
		testVal = Math.pow(10, Math.floor(t) - 1);
		
	var first2Digits = Math.round(spread/testVal);
	
	// Aesthetically choose a scaling factor limiting to a max number of steps 
	var scaleFactor = 1;
	if (first2Digits >= 10 && first2Digits <= 14)
		scaleFactor = 2;
	else if (first2Digits >= 15 && first2Digits <= 19)
		scaleFactor = 3;
	else if (first2Digits >= 20 && first2Digits <= 24)
		scaleFactor = 4;
	else if (first2Digits >= 25 && first2Digits <= 45)
		scaleFactor = 5;
	else if (first2Digits >= 46 && first2Digits <= 80)
		scaleFactor = 10;
	else if (first2Digits >= 81 && first2Digits <= 99)
		scaleFactor = 15; 
	else if (first2Digits == 100)
		scaleFactor = 20;
	else if (first2Digits > 100) {
		// Limit large scale to MAX_NUMBER_OF_GRIDS for step 
		if (testVal > 0)
			scaleFactor = Math.round(spread / DvtDataAxisInfo._MAX_NUMBER_OF_GRIDS_AUTO / testVal);
		else
			scaleFactor = Math.round(spread / DvtDataAxisInfo._MAX_NUMBER_OF_GRIDS_AUTO / 64);
	}
	
	//Adjust based on length of axis
	var scaleUnit;
	var axisLengthRel = (this.EndCoord-this.StartCoord)/100;
	if (axisLengthRel < 1.0) {
		var temp = scaleFactor;
		temp /= axisLengthRel;
		
		while (temp >= 20.0) {
			temp /= 10;
			testVal *= 10;
		}
		
		if (temp < 20.0 && temp > 10.0)
			scaleFactor = 20;
		else if (temp > 5.0)
			scaleFactor = 10;
		else if (temp > 4.0)
			scaleFactor = 5;
		else if (temp > 2.0)
			scaleFactor = 4;
		else
			scaleFactor = 2;
	}
	scaleUnit = scaleFactor * testVal;
	
	return scaleUnit;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getMajorTickCount = function() {
   return this._majorTickCount;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.setMajorTickCount = function(count) {
  this._majorTickCount = count;
  this._majorIncrement = (this._maxValue - this._minValue)/this._majorTickCount;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getMinorTickCount = function() {
   return this._minorTickCount;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.setMinorTickCount = function(count) {
   this._minorTickCount = count;
   this._minorIncrement = this._majorIncrement/this._minorTickCount;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getMajorIncrement = function() {
   return this._majorIncrement;
}

/**
 * @override
 */
DvtDataAxisInfo.prototype.getMinorIncrement = function() {
   return this._minorIncrement;
}

/**
 * Calculated axis information and drawable creation for a group axis.
 * @param {object} data The object containing data for this component.
 * @param {object} options The object containing options specifications for this component.
 * @param {DvtRectangle} availSpace The available space.
 * @class
 * @constructor
 * @extends {DvtAxisInfo}
 */
var DvtGroupAxisInfo = function(data, options, availSpace) {
  this.Init(data, options, availSpace);
}

DvtObj.createSubclass(DvtGroupAxisInfo, DvtAxisInfo, "DvtGroupAxisInfo");

/**
 * @override
 */
DvtGroupAxisInfo.prototype.Init = function(data, options, availSpace) {
  DvtGroupAxisInfo.superclass.Init.call(this, data, options, availSpace);

  // Flip horizontal axes for BIDI
  if((this.Position == "top" || this.Position == "bottom") && DvtStyleUtils.isLocaleR2L()) {
    var temp = this.StartCoord;
    this.StartCoord = this.EndCoord;
    this.EndCoord = temp;
  }

  // Cache the groups
  this._groups = data.groups;
  
  // Calculate the increment and add offsets if specified
  var endOffset = (options.endGroupOffset > 0) ? Number(options.endGroupOffset) : 0;
  this._startOffset = (options.startGroupOffset > 0) ? Number(options.startGroupOffset) : 0;
  this._incr = (this.EndCoord - this.StartCoord)/(this._startOffset + endOffset + this._groups.length - 1);
}

/**
 * @override
 */
DvtGroupAxisInfo.prototype.getLabels = function(context) {
  var labels = [];
  var labelDims = [];
  var container = context.getStage();
  
  // Iterate and create the labels
  var numLabels = this._groups.length;
  for(var i=0; i<numLabels; i++) {
    // Get the label from the group
    var label = this._groups[i];
    if(label && label.name)
      label = label.name;
    
    // Create and position the label
    var coord = this.getCoordAt(i);
    var text = this.CreateLabel(context, label, coord);
    container.addChild(text);
    var dims = text.getDimensions();
    container.removeChild(text);
    labelDims.push(dims);
    labels.push(text);
  }
  return this.SkipLabels(labels, labelDims);
}

/**
 * @override
 */
DvtGroupAxisInfo.prototype.getAxisLine = function(context) {
  var axisLineOptions = this.Options.axisLine;
  if(axisLineOptions.rendered) {
    // Create and return the axis line
    var axisLineStroke = new DvtSolidStroke(axisLineOptions.lineColor, 1, axisLineOptions.lineWidth);
    return this.CreateGridLine(context, axisLineStroke);
  }
  else
    return null;
}

/**
 * @override
 */
DvtGroupAxisInfo.prototype.getGridLines = function(context) {
  var gridlines = [];
  
  // Major Ticks
  var coord, line;
  var majorTickOptions = this.Options.majorTick;
  var majorTickStroke = new DvtSolidStroke(majorTickOptions.lineColor, 1, majorTickOptions.lineWidth);
  if(majorTickOptions.rendered === true) {
    var numGroups = this._groups.length;
    for(var i=0; i<numGroups; i++) {
      coord = this.getCoordAt(i);
      line = this.CreateGridLine(context, majorTickStroke, coord);
      gridlines.push(line);
    }
  }
  
  return gridlines;
}

/**
 * Returns the index for the closest group along the axis.  Returns null if the coordinate
 * is not within the axis.
 * @param {number} coord The coordinate along the axis.
 * @return {number} The index of the closest group.
 */
DvtGroupAxisInfo.prototype.getValueAt = function(coord) {
  var relCoord = coord - this.StartCoord - this._incr*this._startOffset;
  var numIncrements = relCoord/this._incr;
  return Math.round(numIncrements);
}

/**
 * Returns the coordinate for the specified group.
 * @param {object} index The index of the requested group.
 * @return {number} The coordinate for the group.
 */
DvtGroupAxisInfo.prototype.getCoordAt = function(value) {
  if(value < 0)
    return null;
  else
    return this.StartCoord + (this._incr * (this._startOffset + value));
}

/**
 * Returns the index for the closest group along the axis.
 * @param {number} coord The coordinate along the axis.
 * @return {number} The index of the closest group.
 */
DvtGroupAxisInfo.prototype.getBoundedValueAt = function(coord) {
  return null; // TODO Implement
}

/**
 * Returns the coordinate for the specified group.
 * @param {object} index The index of the requested group.
 * @return {number} The coordinate for the group.
 */
DvtGroupAxisInfo.prototype.getBoundedCoordAt = function(value) {
  return null; // TODO Implement
}

/**
 * Returns the coordinate for the specified group.
 * @param {object} index The index of the requested group.
 * @return {number} The coordinate for the group.
 */
DvtGroupAxisInfo.prototype.getUnboundedCoordAt = function(value) {
  return null; // TODO Implement
}
/**
 * Calculated axis information and drawable creation for a time axis.
 * @param {object} data The object containing data for this component.
 * @param {object} options The object containing options specifications for this component.
 * @param {DvtRectangle} availSpace The available space.
 * @class
 * @constructor
 * @extends {DvtAxisInfo}
 */
var DvtTimeAxisInfo = function(data, options, availSpace) {
  this.Init(data, options, availSpace);
}

DvtObj.createSubclass(DvtTimeAxisInfo, DvtAxisInfo, "DvtTimeAxisInfo");

// ------------------------
// Constants
//
DvtTimeAxisInfo.TIME_SECOND      = 1000;
DvtTimeAxisInfo.TIME_MINUTE      = 60 * DvtTimeAxisInfo.TIME_SECOND;
DvtTimeAxisInfo.TIME_HOUR        = 60 * DvtTimeAxisInfo.TIME_MINUTE;
DvtTimeAxisInfo.TIME_DAY         = 24 * DvtTimeAxisInfo.TIME_HOUR;
DvtTimeAxisInfo.TIME_MONTH       = (365/12) * DvtTimeAxisInfo.TIME_DAY;  // approx.
DvtTimeAxisInfo.TIME_YEAR        = 365 * DvtTimeAxisInfo.TIME_DAY;

DvtTimeAxisInfo.INTERVAL_MILLISECONDS = 0;
DvtTimeAxisInfo.INTERVAL_SECONDS      = 1;
DvtTimeAxisInfo.INTERVAL_MINUTE       = 2;
DvtTimeAxisInfo.INTERVAL_HOUR         = 3;
DvtTimeAxisInfo.INTERVAL_DAY          = 4;
DvtTimeAxisInfo.INTERVAL_MONTH        = 5;
DvtTimeAxisInfo.INTERVAL_YEAR         = 6;

DvtTimeAxisInfo.AM_INDEX                 = 12;
DvtTimeAxisInfo.PM_INDEX                 = 13;
DvtTimeAxisInfo.AMPM_BEFORE_INDEX        = 14;
DvtTimeAxisInfo.DMY_ORDER_INDEX          = 15;
DvtTimeAxisInfo.YEAR_TRAILING_CHAR_INDEX = 16;
DvtTimeAxisInfo.DAY_TRAILING_CHAR_INDEX  = 17;

/**
 * @override
 */
DvtTimeAxisInfo.prototype.Init = function(data, options, availSpace) {
  DvtTimeAxisInfo.superclass.Init.call(this, data, options, availSpace);

  // TODO Implement
  
  // TODO Support BIDI
  // Flip horizontal axes for BIDI
  // Figure out the coords for the min/max values
  if(this.Position == "top" || this.Position == "bottom") {
    // Axis is horizontal, so flip for BIDI if needed
    if(DvtStyleUtils.isLocaleR2L()) {
      this._minCoord = this.EndCoord;
      this._maxCoord = this.StartCoord;
    }
    else {
      this._minCoord = this.StartCoord;
      this._maxCoord = this.EndCoord;
    }
  }
  else {
    this._minCoord = this.EndCoord;
    this._maxCoord = this.StartCoord;
  }
  this._minValue = data.minDataValue;
  this._maxValue = data.maxDataValue;

  this._converter = null;
  if (options.tickLabel !== undefined) {
    this._converter = options.tickLabel.converter;
  }
  
  this._timeZoneOffset = 0;
  this._innerIncrement = this._calcInnerIncrement(this._minValue, this._maxValue);
}

DvtTimeAxisInfo.prototype.formatLabel = function(axisValue) {
    // First convert to a date object
    var date = new Date(axisValue + this._timeZoneOffset);
    
    // If dateTimeFormatter is used, use it
    if (this._converter && this._converter.getAsString && this._converter.getAsObject)
        return this._converter.getAsString(date);

    if(this._innerIncrement === DvtTimeAxisInfo.TIME_DAY)
        return this._formatTime(date);
     
    // date.toString() returns "Day Mon Date HH:MM:SS TZD YYYY"     
    var dateToString  = date.toString();   
    var currIndex = dateToString.indexOf(" ", 0); // space after Day
    // Remove the Day from the string since we don't use it
    // Now the dateToString is "Mon Date HH:MM:SS TZD YYYY"
    dateToString = dateToString.substring(currIndex + 1, dateToString.length);
    currIndex = dateToString.indexOf(" ", 0); // space after Month
    var nextIndex = dateToString.indexOf(" ", currIndex + 1); // space after Date
          
    var dateStr = dateToString.substring(currIndex + 1, nextIndex);
    
    var yearStr = date.getFullYear();
    var monthStr;
    if(this._timeAxisResources && this._timeAxisResources.length >= 12)
        monthStr = this._timeAxisResources[date.getMonth()];
    else 
        monthStr = dateToString.substring(0, currIndex);;
        
    // Add the day and year trailing characters if needed
    if(this._timeAxisResources && this._timeAxisResources.length > DvtTimeAxisInfo.DAY_TRAILING_CHAR_INDEX) {
        var yearChar = this._timeAxisResources[DvtTimeAxisInfo.YEAR_TRAILING_CHAR_INDEX];
        var dayChar = this._timeAxisResources[DvtTimeAxisInfo.DAY_TRAILING_CHAR_INDEX];
        // These will be "" if not needed
        yearStr += yearChar;
        dateStr += dayChar; 
    }

    // Process the DMY Order
    var isDateMonth = true;
    var isMonthYear = true;
    if(this._timeAxisResources && this._timeAxisResources.length > DvtTimeAxisInfo.DMY_ORDER_INDEX) {
        var DMY = this._timeAxisResources[DvtTimeAxisInfo.DMY_ORDER_INDEX];
        isDateMonth = (DMY.indexOf("D") < DMY.indexOf("M"));
        isMonthYear = (DMY.indexOf("M") < DMY.indexOf("Y"));
    }
      
    if(this._innerIncrement === DvtTimeAxisInfo.TIME_MONTH) { // return Date
        if(isDateMonth)
            return dateStr + " " + monthStr;
        else
            return monthStr + " " + dateStr;
    }
    
    if(this._innerIncrement === DvtTimeAxisInfo.TIME_YEAR) { // return Month
        if(isMonthYear)
            return monthStr + " " + yearStr;
        else
            return yearStr + " " + monthStr;
    }
    
    return yearStr;
}

/**
  * Returns the time as a String, formatted in the form HH:MM:SS AM/PM.
  */
DvtTimeAxisInfo.prototype._formatTime = function(date) {

    var hours = date.getHours();
    var mins = date.getMinutes();
    var secs = date.getSeconds();

    var am = "";
    var pm = "";
    var ampmBefore = false;

    if (this._timeAxisResources != null && this._timeAxisResources > DvtTimeAxisInfo.AMPM_BEFORE_INDEX) {
        am = this._timeAxisResources[DvtTimeAxis.AM_INDEX];
        pm = this._timeAxisResources[DvtTimeAxis.PM_INDEX];
        ampmBefore = this._timeAxisResources[DvtTimeAxisInfo.AMPM_BEFORE_INDEX] == "t";
    }

    var b12HFormat = (am != "" && pm != "");
    var ampm;

    if (b12HFormat) {
        ampm = pm;
        if (hours > 12) {
            hours -= 12;
            ampm = pm;
        }
        else if (hours == 0) {
            ampm = am;
            hours = 12;
        }
        else if (hours < 12) {
            ampm = am;
        }
    }

    var timeLabel = this._doubleDigit(hours) + ":" + this._doubleDigit(mins) + ":" + this._doubleDigit(secs);

    if (b12HFormat) {
        if (ampmBefore)
            return ampm + " " + timeLabel;
        else
            return timeLabel + " " + ampm;
    }
    else {
        return timeLabel;
    }
}


DvtTimeAxisInfo.prototype._doubleDigit = function(num) {
    if (num < 10) {
        return "0" + num;
    }
    return "" + num;
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getLabels = function(context) {
  // TODO Implement
  var labels = [];
  var labelDims = [];
  var container = context.getStage();
  
  // Iterate on an integer to reduce rounding error.  We use <= since the first
  // tick is not counted in the tick count.
  for(var i=this._minValue; i<=this._maxValue; i+= this._innerIncrement) {
    var label = this.formatLabel(i);
    var coord = this.getCoordAt(i);
    var text = this.CreateLabel(context, label, coord);
    container.addChild(text);
    var dims = text.getDimensions();
    container.removeChild(text);
    labelDims.push(dims);
    labels.push(text);
  }
  return this.SkipLabels(labels, labelDims);
}

/**
 * Given the min and max values on the scale, calculates and returns the inner
 * grip step increments.
 *
 * @param scaleMin  the minimum value on the scale
 * @param scaleMax  the maximum value on the scale
 */
DvtTimeAxisInfo.prototype._calcInnerIncrement = function(scaleMin, scaleMax) {

    var spread = scaleMax - scaleMin;
    var intervalSize;

    if (spread < DvtTimeAxisInfo.TIME_MINUTE)
        intervalSize = DvtTimeAxisInfo.TIME_MINUTE;
    else if (spread < DvtTimeAxisInfo.TIME_HOUR)
        intervalSize = DvtTimeAxisInfo.TIME_HOUR;
    else if (spread < DvtTimeAxisInfo.TIME_DAY)
        intervalSize = DvtTimeAxisInfo.TIME_DAY;
    else if (spread < DvtTimeAxisInfo.TIME_MONTH)
        intervalSize = DvtTimeAxisInfo.TIME_MONTH;
    else if (spread < DvtTimeAxisInfo.TIME_YEAR)
        intervalSize = DvtTimeAxisInfo.TIME_YEAR;
    else 
        intervalSize = DvtTimeAxisInfo.TIME_YEAR;

    return intervalSize;
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getAxisLine = function(context) {
  var axisLineOptions = this.Options.axisLine;
  if(axisLineOptions.rendered) {
    // TODO hzhang Check Axis Line behavior for negative/mixed axes.
    var axisLineStroke = new DvtSolidStroke(axisLineOptions.lineColor, 1, axisLineOptions.lineWidth);
    return this.CreateGridLine(context, axisLineStroke, 10);
  }
  else 
    return null;
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getGridLines = function(context) {
  var gridlines = [];
  
  // Major Ticks
  var coord, line;
  var majorTickOptions = this.Options.majorTick;
  var majorTickStroke = new DvtSolidStroke(majorTickOptions.lineColor, 1, majorTickOptions.lineWidth);
  if(majorTickOptions.rendered === true) {
    var numGroups = this._groups.length;
    for(var i=0; i<numGroups; i++) {
      coord = this.getCoordAt(i);
      line = this.CreateGridLine(context, majorTickStroke, coord);
      gridlines.push(line);
    }
  }
  
  return gridlines;
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getValueAt = function(coord) {
  // Return null if the coord is outside of the axis
  if(coord < this._minCoord || coord > this._maxCoord)
    return null;
  
  // Otherwise find the value
  var ratio = (coord - this._minCoord)/(this._maxCoord - this._minCoord);
  return this._minValue + (ratio * (this._maxValue - this._minValue));
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getCoordAt = function(value) {
  // Return null if the value is outside of the axis
  if(value < this._minValue || value > this._maxValue)
    return null;

  return this.getUnboundedCoordAt(value);
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getBoundedValueAt = function(coord) {
  if(coord < this._minCoord)
    coord = this._minCoord;
  else if(coord > this._maxCoord)
    coord = this._maxCoord;

  return this.getValueAt(coord);
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getBoundedCoordAt = function(value) {
  if(value < this._minValue)
    value = this._minValue;
  else if(value > this._maxValue)
    value = this._maxValue;

  return this.getUnboundedCoordAt(value);
}

/**
 * @override
 */
DvtTimeAxisInfo.prototype.getUnboundedCoordAt = function(value) {
  var ratio = (value - this._minValue)/(this._maxValue - this._minValue);
  return this._minCoord + (ratio * (this._maxCoord - this._minCoord));
}
