Package com.positive.charts.axis

Source Code of com.positive.charts.axis.DateAxis$DefaultTimeline

package com.positive.charts.axis;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;

import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;

import com.positive.charts.axis.ticks.DateTick;
import com.positive.charts.axis.ticks.DateTickUnit;
import com.positive.charts.axis.ticks.ITick;
import com.positive.charts.axis.ticks.TickUnit;
import com.positive.charts.axis.ticks.TickUnitSource;
import com.positive.charts.axis.ticks.TickUnits;
import com.positive.charts.block.RectangleInsets;
import com.positive.charts.common.RectangleEdge;
import com.positive.charts.data.Range;
import com.positive.charts.data.time.DateRange;
import com.positive.charts.data.time.Month;
import com.positive.charts.data.time.RegularTimePeriod;
import com.positive.charts.data.time.Year;
import com.positive.charts.event.AxisChangeEvent;
import com.positive.charts.plot.Plot;
import com.positive.charts.plot.PlotRenderingInfo;
import com.positive.charts.plot.ValueAxisPlot;
import com.positive.charts.util.RectangleUtil;
import com.positive.charts.util.TextAnchor;

/**
* The base class for axes that display dates. You will find it easier to
* understand how this axis works if you bear in mind that it really
* displays/measures integer (or long) data, where the integers are milliseconds
* since midnight, 1-Jan-1970. When displaying tick labels, the millisecond
* values are converted back to dates using a <code>DateFormat</code> instance.
* <P>
* You can also create a {@link org.jfree.chart.axis.Timeline} and supply in the
* constructor to create an axis that only contains certain domain values. For
* example, this allows you to create a date axis that only contains working
* days.
*/
public class DateAxis extends ValueAxis {

  /**
   * A timeline that includes all milliseconds (as defined by
   * <code>java.util.Date</code>) in the real time line.
   */
  private static class DefaultTimeline implements Timeline {

    /**
     * Returns <code>true</code> if the timeline includes the specified
     * domain value range.
     *
     * @param from
     *            the start date.
     * @param to
     *            the end date.
     *
     * @return <code>true</code>.
     */
    public boolean containsDomainRange(final Date from, final Date to) {
      return true;
    }

    /**
     * Returns <code>true</code> if the timeline includes the specified
     * domain value range.
     *
     * @param from
     *            the start value.
     * @param to
     *            the end value.
     *
     * @return <code>true</code>.
     */
    public boolean containsDomainRange(final long from, final long to) {
      return true;
    }

    /**
     * Returns <code>true</code> if the timeline includes the specified
     * domain value.
     *
     * @param date
     *            the date.
     *
     * @return <code>true</code>.
     */
    public boolean containsDomainValue(final Date date) {
      return true;
    }

    /**
     * Returns <code>true</code> if the timeline includes the specified
     * domain value.
     *
     * @param millisecond
     *            the millisecond.
     *
     * @return <code>true</code>.
     */
    public boolean containsDomainValue(final long millisecond) {
      return true;
    }

    /**
     * Tests an object for equality with this instance.
     *
     * @param object
     *            the object.
     *
     * @return A boolean.
     */
    public boolean equals(final Object object) {
      if (object == null) {
        return false;
      }

      if (object == this) {
        return true;
      }

      if (object instanceof DefaultTimeline) {
        return true;
      }

      return false;
    }

    /**
     * Converts a timeline value into a millisecond (as encoded by
     * <code>java.util.Date</code>).
     *
     * @param value
     *            the value.
     *
     * @return The millisecond.
     */
    public long toMillisecond(final long value) {
      return value;
    }

    /**
     * Converts a date into a timeline value.
     *
     * @param date
     *            the domain value.
     *
     * @return The timeline value.
     */
    public long toTimelineValue(final Date date) {
      return date.getTime();
    }

    /**
     * Converts a millisecond into a timeline value.
     *
     * @param millisecond
     *            the millisecond.
     *
     * @return The timeline value.
     */
    public long toTimelineValue(final long millisecond) {
      return millisecond;
    }
  }

  /** The default axis range. */
  public static final DateRange DEFAULT_DATE_RANGE = new DateRange();

  /** The default minimum auto range size. */
  public static final double DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS = 2.0;

  /** The default date tick unit. */
  public static final DateTickUnit DEFAULT_DATE_TICK_UNIT = new DateTickUnit(
      DateTickUnit.DAY, 1, new SimpleDateFormat());

  /** The default anchor date. */
  public static final Date DEFAULT_ANCHOR_DATE = new Date();

  /**
   * Returns a collection of standard date tick units that uses the default
   * time zone. This collection will be used by default, but you are free to
   * create your own collection if you want to (see the
   * {@link ValueAxis#setStandardTickUnits(TickUnitSource)} method inherited
   * from the {@link ValueAxis} class).
   *
   * @return A collection of standard date tick units.
   */
  public static TickUnitSource createStandardDateTickUnits() {
    return createStandardDateTickUnits(TimeZone.getDefault());
  }

  /**
   * Returns a collection of standard date tick units. This collection will be
   * used by default, but you are free to create your own collection if you
   * want to (see the {@link ValueAxis#setStandardTickUnits(TickUnitSource)}
   * method inherited from the {@link ValueAxis} class).
   *
   * @param zone
   *            the time zone (<code>null</code> not permitted).
   *
   * @return A collection of standard date tick units.
   */
  public static TickUnitSource createStandardDateTickUnits(final TimeZone zone) {

    if (zone == null) {
      throw new IllegalArgumentException("Null 'zone' argument.");
    }
    final TickUnits units = new TickUnits();

    // date formatters
    final DateFormat f1 = new SimpleDateFormat("HH:mm:ss.SSS");
    final DateFormat f2 = new SimpleDateFormat("HH:mm:ss");
    final DateFormat f3 = new SimpleDateFormat("HH:mm");
    final DateFormat f4 = new SimpleDateFormat("d-MMM, HH:mm");
    final DateFormat f5 = new SimpleDateFormat("d-MMM");
    final DateFormat f6 = new SimpleDateFormat("MMM-yyyy");
    final DateFormat f7 = new SimpleDateFormat("yyyy");

    f1.setTimeZone(zone);
    f2.setTimeZone(zone);
    f3.setTimeZone(zone);
    f4.setTimeZone(zone);
    f5.setTimeZone(zone);
    f6.setTimeZone(zone);
    f7.setTimeZone(zone);

    // milliseconds
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 1, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 5,
        DateTickUnit.MILLISECOND, 1, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 10,
        DateTickUnit.MILLISECOND, 1, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 25,
        DateTickUnit.MILLISECOND, 5, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 50,
        DateTickUnit.MILLISECOND, 10, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 100,
        DateTickUnit.MILLISECOND, 10, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 250,
        DateTickUnit.MILLISECOND, 10, f1));
    units.add(new DateTickUnit(DateTickUnit.MILLISECOND, 500,
        DateTickUnit.MILLISECOND, 50, f1));

    // seconds
    units.add(new DateTickUnit(DateTickUnit.SECOND, 1,
        DateTickUnit.MILLISECOND, 50, f2));
    units.add(new DateTickUnit(DateTickUnit.SECOND, 5, DateTickUnit.SECOND,
        1, f2));
    units.add(new DateTickUnit(DateTickUnit.SECOND, 10,
        DateTickUnit.SECOND, 1, f2));
    units.add(new DateTickUnit(DateTickUnit.SECOND, 30,
        DateTickUnit.SECOND, 5, f2));

    // minutes
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 1, DateTickUnit.SECOND,
        5, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 2, DateTickUnit.SECOND,
        10, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 5, DateTickUnit.MINUTE,
        1, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 10,
        DateTickUnit.MINUTE, 1, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 15,
        DateTickUnit.MINUTE, 5, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 20,
        DateTickUnit.MINUTE, 5, f3));
    units.add(new DateTickUnit(DateTickUnit.MINUTE, 30,
        DateTickUnit.MINUTE, 5, f3));

    // hours
    units.add(new DateTickUnit(DateTickUnit.HOUR, 1, DateTickUnit.MINUTE,
        5, f3));
    units.add(new DateTickUnit(DateTickUnit.HOUR, 2, DateTickUnit.MINUTE,
        10, f3));
    units.add(new DateTickUnit(DateTickUnit.HOUR, 4, DateTickUnit.MINUTE,
        30, f3));
    units.add(new DateTickUnit(DateTickUnit.HOUR, 6, DateTickUnit.HOUR, 1,
        f3));
    units.add(new DateTickUnit(DateTickUnit.HOUR, 12, DateTickUnit.HOUR, 1,
        f4));

    // days
    units.add(new DateTickUnit(DateTickUnit.DAY, 1, DateTickUnit.HOUR, 1,
        f5));
    units.add(new DateTickUnit(DateTickUnit.DAY, 2, DateTickUnit.HOUR, 1,
        f5));
    units
        .add(new DateTickUnit(DateTickUnit.DAY, 7, DateTickUnit.DAY, 1,
            f5));
    units.add(new DateTickUnit(DateTickUnit.DAY, 15, DateTickUnit.DAY, 1,
        f5));

    // months
    units.add(new DateTickUnit(DateTickUnit.MONTH, 1, DateTickUnit.DAY, 1,
        f6));
    units.add(new DateTickUnit(DateTickUnit.MONTH, 2, DateTickUnit.DAY, 1,
        f6));
    units.add(new DateTickUnit(DateTickUnit.MONTH, 3, DateTickUnit.MONTH,
        1, f6));
    units.add(new DateTickUnit(DateTickUnit.MONTH, 4, DateTickUnit.MONTH,
        1, f6));
    units.add(new DateTickUnit(DateTickUnit.MONTH, 6, DateTickUnit.MONTH,
        1, f6));

    // years
    units.add(new DateTickUnit(DateTickUnit.YEAR, 1, DateTickUnit.MONTH, 1,
        f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 2, DateTickUnit.MONTH, 3,
        f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 5, DateTickUnit.YEAR, 1,
        f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 10, DateTickUnit.YEAR, 1,
        f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 25, DateTickUnit.YEAR, 5,
        f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 50, DateTickUnit.YEAR,
        10, f7));
    units.add(new DateTickUnit(DateTickUnit.YEAR, 100, DateTickUnit.YEAR,
        20, f7));

    return units;

  }

  /** The current tick unit. */
  private DateTickUnit tickUnit;

  /** The override date format. */
  private DateFormat dateFormatOverride;

  /**
   * Tick marks can be displayed at the start or the middle of the time
   * period.
   */
  private DateTick.MarkPosition tickMarkPosition = DateTick.MarkPosition.START;

  /** A static default timeline shared by all standard DateAxis */
  private static final Timeline DEFAULT_TIMELINE = new DefaultTimeline();

  /** The time zone for the axis. */
  private final TimeZone timeZone;

  /** Our underlying timeline. */
  private Timeline timeline;

  /**
   * Creates a date axis with no label.
   */
  public DateAxis() {
    this(null);
  }

  /**
   * Creates a date axis with the specified label.
   *
   * @param label
   *            the axis label (<code>null</code> permitted).
   */
  public DateAxis(final String label) {
    this(label, TimeZone.getDefault());
  }

  /**
   * Creates a date axis. A timeline is specified for the axis. This allows
   * special transformations to occur between a domain of values and the
   * values included in the axis.
   *
   * @see org.jfree.chart.axis.SegmentedTimeline
   *
   * @param label
   *            the axis label (<code>null</code> permitted).
   * @param zone
   *            the time zone.
   */
  public DateAxis(final String label, final TimeZone zone) {
    super(label, DateAxis.createStandardDateTickUnits(zone));
    this.setTickUnit(DateAxis.DEFAULT_DATE_TICK_UNIT, false, false);
    this
        .setAutoRangeMinimumSize(DEFAULT_AUTO_RANGE_MINIMUM_SIZE_IN_MILLISECONDS);
    this.setRange(DEFAULT_DATE_RANGE, false, false);
    this.dateFormatOverride = null;
    this.timeZone = zone;
    this.timeline = DEFAULT_TIMELINE;
  }

  /**
   * Rescales the axis to ensure that all data is visible.
   */
  protected void autoAdjustRange() {

    final Plot plot = this.getPlot();

    if (plot == null) {
      return; // no plot, no data
    }

    if (plot instanceof ValueAxisPlot) {
      final ValueAxisPlot vap = (ValueAxisPlot) plot;

      Range r = vap.getDataRange(this);
      if (r == null) {
        if (this.timeline instanceof SegmentedTimeline) {
          // Timeline hasn't method getStartTime()
          r = new DateRange(
              ((SegmentedTimeline) this.timeline).getStartTime(),
              ((SegmentedTimeline) this.timeline).getStartTime() + 1);
        } else {
          r = new DateRange();
        }
      }

      long upper = this.timeline
          .toTimelineValue((long) r.getUpperBound());
      long lower;
      final long fixedAutoRange = (long) this.getFixedAutoRange();
      if (fixedAutoRange > 0.0) {
        lower = upper - fixedAutoRange;
      } else {
        lower = this.timeline.toTimelineValue((long) r.getLowerBound());
        final double range = upper - lower;
        final long minRange = (long) this.getAutoRangeMinimumSize();
        if (range < minRange) {
          final long expand = (long) (minRange - range) / 2;
          upper = upper + expand;
          lower = lower - expand;
        }
        upper = upper + (long) (range * this.getUpperMargin());
        lower = lower - (long) (range * this.getLowerMargin());
      }

      upper = this.timeline.toMillisecond(upper);
      lower = this.timeline.toMillisecond(lower);
      final DateRange dr = new DateRange(new Date(lower), new Date(upper));
      this.setRange(dr, false, false);
    }

  }

  /**
   * Returns a {@link java.util.Date} corresponding to the specified position
   * within a {@link RegularTimePeriod}.
   *
   * @param period
   *            the period.
   * @param position
   *            the position (<code>null</code> not permitted).
   *
   * @return A date.
   */
  private Date calculateDateForPosition(final RegularTimePeriod period,
      final DateTick.MarkPosition position) {

    if (position == null) {
      throw new IllegalArgumentException("Null 'position' argument.");
    }
    Date result = null;
    if (position == DateTick.MarkPosition.START) {
      result = new Date(period.getFirstMillisecond());
    } else if (position == DateTick.MarkPosition.MIDDLE) {
      result = new Date(period.getMiddleMillisecond());
    } else if (position == DateTick.MarkPosition.END) {
      result = new Date(period.getLastMillisecond());
    }
    return result;

  }

  /**
   * Calculates the value of the highest visible tick on the axis.
   *
   * @param unit
   *            date unit to use.
   *
   * @return The value of the highest visible tick on the axis.
   */
  public Date calculateHighestVisibleTickValue(final DateTickUnit unit) {
    return this.previousStandardDate(this.getMaximumDate(), unit);
  }

  /**
   * Calculates the value of the lowest visible tick on the axis.
   *
   * @param unit
   *            date unit to use.
   *
   * @return The value of the lowest visible tick on the axis.
   */
  public Date calculateLowestVisibleTickValue(final DateTickUnit unit) {
    return this.nextStandardDate(this.getMinimumDate(), unit);
  }

  /**
   * Configures the axis to work with the specified plot. If the axis has
   * auto-scaling, then sets the maximum and minimum values.
   */
  public void configure() {
    if (this.isAutoRange()) {
      this.autoAdjustRange();
    }
  }

  /**
   * Translates a date to Java2D coordinates, based on the range displayed by
   * this axis for the specified data area.
   *
   * @param date
   *            the date.
   * @param area
   *            the rectangle (in Java2D space) where the data is to be
   *            plotted.
   * @param edge
   *            the axis location.
   *
   * @return The coordinate corresponding to the supplied date.
   */
  public double dateToJava2D(final Date date, final Rectangle area,
      final RectangleEdge edge) {
    final double value = date.getTime();
    return this.valueToJava2D(value, area, edge);
  }

  /**
   * Draws the axis on a Java 2D graphics device (such as the screen or a
   * printer).
   *
   * @param g2
   *            the graphics device (<code>null</code> not permitted).
   * @param cursor
   *            the cursor location.
   * @param plotArea
   *            the area within which the axes and data should be drawn (
   *            <code>null</code> not permitted).
   * @param dataArea
   *            the area within which the data should be drawn (
   *            <code>null</code> not permitted).
   * @param edge
   *            the location of the axis (<code>null</code> not permitted).
   * @param plotState
   *            collects information about the plot (<code>null</code>
   *            permitted).
   *
   * @return The axis state (never <code>null</code>).
   */
  public AxisState draw(final GC g2, final double cursor,
      final Rectangle plotArea, final Rectangle dataArea,
      final RectangleEdge edge, final PlotRenderingInfo plotState) {

    // if the axis is not visible, don't draw it...
    if (!this.isVisible()) {
      final AxisState state = new AxisState(cursor);
      // even though the axis is not visible, we need to refresh ticks in
      // case the grid is being drawn...
      final List ticks = this.refreshTicks(g2, state, dataArea, edge);
      state.setTicks(ticks);
      return state;
    }

    // draw the tick marks and labels...
    AxisState state = this.drawTickMarksAndLabels(g2, cursor, plotArea,
        dataArea, edge);

    // draw the axis label (note that 'state' is passed in *and*
    // returned)...
    state = this.drawLabel(this.getLabel(), g2, plotArea, dataArea, edge,
        state);

    return state;

  }

  /**
   * Estimates the maximum width of the tick labels, assuming the specified
   * tick unit is used.
   * <P>
   * Rather than computing the string bounds of every tick on the axis, we
   * just look at two values: the lower bound and the upper bound for the
   * axis. These two values will usually be representative.
   *
   * @param g2
   *            the graphics device.
   * @param unit
   *            the tick unit to use for calculation.
   *
   * @return The estimated maximum width of the tick labels.
   */
  private double estimateMaximumTickLabelHeight(final GC g2,
      final DateTickUnit unit) {

    final RectangleInsets tickLabelInsets = this.getTickLabelInsets();
    double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);
    final FontMetrics lm = g2.getFontMetrics();
    if (!this.isVerticalTickLabels()) {
      // all tick labels have the same width (equal to the height of
      // the font)...
      result += lm.getHeight();
    } else {
      // look at lower and upper bounds...
      final DateRange range = (DateRange) this.getRange();
      final Date lower = range.getLowerDate();
      final Date upper = range.getUpperDate();
      String lowerStr = null;
      String upperStr = null;
      final DateFormat formatter = this.getDateFormatOverride();
      if (formatter != null) {
        lowerStr = formatter.format(lower);
        upperStr = formatter.format(upper);
      } else {
        lowerStr = unit.dateToString(lower);
        upperStr = unit.dateToString(upper);
      }
      final double w1 = g2.textExtent(lowerStr).x;
      final double w2 = g2.textExtent(upperStr).x;
      result += Math.max(w1, w2);
    }

    return result;

  }

  /**
   * Estimates the maximum width of the tick labels, assuming the specified
   * tick unit is used.
   * <P>
   * Rather than computing the string bounds of every tick on the axis, we
   * just look at two values: the lower bound and the upper bound for the
   * axis. These two values will usually be representative.
   *
   * @param g2
   *            the graphics device.
   * @param unit
   *            the tick unit to use for calculation.
   *
   * @return The estimated maximum width of the tick labels.
   */
  private double estimateMaximumTickLabelWidth(final GC g2,
      final DateTickUnit unit) {

    final RectangleInsets tickLabelInsets = this.getTickLabelInsets();
    double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);
    // FontRenderContext frc = g2.getFontRenderContext();
    // LineMetrics lm = tickLabelFont.getLineMetrics("ABCxyz", frc);
    final FontMetrics lm = g2.getFontMetrics();
    if (this.isVerticalTickLabels()) {
      // all tick labels have the same width (equal to the height of
      // the font)...
      result += lm.getHeight();
    } else {
      // look at lower and upper bounds...
      final DateRange range = (DateRange) this.getRange();
      final Date lower = range.getLowerDate();
      final Date upper = range.getUpperDate();
      String lowerStr = null;
      String upperStr = null;
      final DateFormat formatter = this.getDateFormatOverride();
      if (formatter != null) {
        lowerStr = formatter.format(lower);
        upperStr = formatter.format(upper);
      } else {
        lowerStr = unit.dateToString(lower);
        upperStr = unit.dateToString(upper);
      }
      final double w1 = g2.textExtent(lowerStr).x;
      final double w2 = g2.textExtent(upperStr).x;
      result += Math.max(w1, w2);
    }

    return result;

  }

  /**
   * Returns the date format override. If this is non-null, then it will be
   * used to format the dates on the axis.
   *
   * @return The formatter (possibly <code>null</code>).
   */
  public DateFormat getDateFormatOverride() {
    return this.dateFormatOverride;
  }

  /**
   * Returns the latest date visible on the axis.
   *
   * @return The date.
   */
  public Date getMaximumDate() {

    Date result = null;
    final Range range = this.getRange();
    if (range instanceof DateRange) {
      final DateRange r = (DateRange) range;
      result = r.getUpperDate();
    } else {
      result = new Date((long) range.getUpperBound());
    }
    return result;

  }

  /**
   * Returns the earliest date visible on the axis.
   *
   * @return The date.
   */
  public Date getMinimumDate() {

    Date result = null;

    final Range range = this.getRange();
    if (range instanceof DateRange) {
      final DateRange r = (DateRange) range;
      result = r.getLowerDate();
    } else {
      result = new Date((long) range.getLowerBound());
    }

    return result;

  }

  /**
   * Returns the tick mark position (start, middle or end of the time period).
   *
   * @return The position (never <code>null</code>).
   */
  public DateTick.MarkPosition getTickMarkPosition() {
    return this.tickMarkPosition;
  }

  /**
   * Returns the tick unit for the axis.
   *
   * @return The tick unit (possibly <code>null</code>).
   */
  public DateTickUnit getTickUnit() {
    return this.tickUnit;
  }

  /**
   * Returns the underlying timeline used by this axis.
   *
   * @return The timeline.
   */
  public Timeline getTimeline() {
    return this.timeline;
  }

  private boolean hasDuplicates() {

    new java.util.ArrayList();

    final DateTickUnit unit = this.getTickUnit();
    Date tickDate = this.calculateLowestVisibleTickValue(unit);

    String previousTickLabel = "";
    String tickLabel = "";

    final Date upperDate = this.getMaximumDate();

    while (tickDate.before(upperDate)) {
      if (!this.isHiddenValue(tickDate.getTime())) {

        final DateFormat formatter = this.getDateFormatOverride();
        if (formatter != null) {
          tickLabel = formatter.format(tickDate);
        } else {
          tickLabel = this.tickUnit.dateToString(tickDate);
        }

        // TODO: Code below fixes duplicated labels problem
        if (tickLabel.equals(previousTickLabel)) {
          tickLabel = "";
          return true;
        } else {
          previousTickLabel = tickLabel;
        }

        tickDate = unit.addToDate(tickDate);
      } else {
        tickDate = unit.rollDate(tickDate);
      }
    }

    return false;
  }

  /**
   * Returns a hash code for this object.
   *
   * @return A hash code.
   */
  public int hashCode() {
    if (this.getLabel() != null) {
      return this.getLabel().hashCode();
    } else {
      return 0;
    }
  }

  /**
   * Returns <code>true</code> if the axis hides this value, and
   * <code>false</code> otherwise.
   *
   * @param millis
   *            the data value.
   *
   * @return A value.
   */
  public boolean isHiddenValue(final long millis) {
    return (!this.timeline.containsDomainValue(new Date(millis)));
  }

  /**
   * Translates a Java2D coordinate into the corresponding data value. To
   * perform this translation, you need to know the area used for plotting
   * data, and which edge the axis is located on.
   *
   * @param java2DValue
   *            the coordinate in Java2D space.
   * @param area
   *            the rectangle (in Java2D space) where the data is to be
   *            plotted.
   * @param edge
   *            the axis location.
   *
   * @return A data value.
   */
  public double java2DToValue(final double java2DValue, final Rectangle area,
      final RectangleEdge edge) {

    final DateRange range = (DateRange) this.getRange();
    final double axisMin = this.timeline.toTimelineValue(range
        .getLowerDate());
    final double axisMax = this.timeline.toTimelineValue(range
        .getUpperDate());

    double min = 0.0;
    double max = 0.0;
    if (edge.isTopOrBottom()) {
      min = area.x;
      max = RectangleUtil.getMaxX(area);
    } else if (edge.isLeftOrRight()) {
      min = RectangleUtil.getMaxY(area);
      max = area.y;
    }

    double result;
    if (this.isInverted()) {
      result = axisMax
          - ((java2DValue - min) / (max - min) * (axisMax - axisMin));
    } else {
      result = axisMin
          + ((java2DValue - min) / (max - min) * (axisMax - axisMin));
    }

    return this.timeline.toMillisecond((long) result);
  }

  /**
   * Returns the first "standard" date (based on the specified field and
   * units).
   *
   * @param date
   *            the reference date.
   * @param unit
   *            the date tick unit.
   *
   * @return The next "standard" date.
   */
  protected Date nextStandardDate(final Date date, final DateTickUnit unit) {

    final Date previous = this.previousStandardDate(date, unit);
    final Calendar calendar = Calendar.getInstance();
    calendar.setTime(previous);
    calendar.add(unit.getCalendarField(), unit.getCount());
    return calendar.getTime();

  }

  /**
   * Returns the previous "standard" date, for a given date and tick unit.
   *
   * @param date
   *            the reference date.
   * @param unit
   *            the tick unit.
   *
   * @return The previous "standard" date.
   */
  protected Date previousStandardDate(final Date date, final DateTickUnit unit) {
    int milliseconds;
    int seconds;
    int minutes;
    int hours;
    int days;
    int months;
    int years;

    final Calendar calendar = Calendar.getInstance(this.timeZone);
    calendar.setTime(date);
    final int count = unit.getCount();
    final int current = calendar.get(unit.getCalendarField());
    final int value = count * (current / count);

    switch (unit.getUnit()) {

    case (DateTickUnit.MILLISECOND):
      years = calendar.get(Calendar.YEAR);
      months = calendar.get(Calendar.MONTH);
      days = calendar.get(Calendar.DATE);
      hours = calendar.get(Calendar.HOUR_OF_DAY);
      minutes = calendar.get(Calendar.MINUTE);
      seconds = calendar.get(Calendar.SECOND);
      calendar.set(years, months, days, hours, minutes, seconds);
      calendar.set(Calendar.MILLISECOND, value);
      return calendar.getTime();

    case (DateTickUnit.SECOND):
      years = calendar.get(Calendar.YEAR);
      months = calendar.get(Calendar.MONTH);
      days = calendar.get(Calendar.DATE);
      hours = calendar.get(Calendar.HOUR_OF_DAY);
      minutes = calendar.get(Calendar.MINUTE);
      if (this.tickMarkPosition == DateTick.MarkPosition.START) {
        milliseconds = 0;
      } else if (this.tickMarkPosition == DateTick.MarkPosition.MIDDLE) {
        milliseconds = 500;
      } else {
        milliseconds = 999;
      }
      calendar.set(Calendar.MILLISECOND, milliseconds);
      calendar.set(years, months, days, hours, minutes, value);
      return calendar.getTime();

    case (DateTickUnit.MINUTE):
      years = calendar.get(Calendar.YEAR);
      months = calendar.get(Calendar.MONTH);
      days = calendar.get(Calendar.DATE);
      hours = calendar.get(Calendar.HOUR_OF_DAY);
      if (this.tickMarkPosition == DateTick.MarkPosition.START) {
        seconds = 0;
      } else if (this.tickMarkPosition == DateTick.MarkPosition.MIDDLE) {
        seconds = 30;
      } else {
        seconds = 59;
      }
      calendar.clear(Calendar.MILLISECOND);
      calendar.set(years, months, days, hours, value, seconds);
      return calendar.getTime();

    case (DateTickUnit.HOUR):
      years = calendar.get(Calendar.YEAR);
      months = calendar.get(Calendar.MONTH);
      days = calendar.get(Calendar.DATE);
      if (this.tickMarkPosition == DateTick.MarkPosition.START) {
        minutes = 0;
        seconds = 0;
      } else if (this.tickMarkPosition == DateTick.MarkPosition.MIDDLE) {
        minutes = 30;
        seconds = 0;
      } else {
        minutes = 59;
        seconds = 59;
      }
      calendar.clear(Calendar.MILLISECOND);
      calendar.set(years, months, days, value, minutes, seconds);
      return calendar.getTime();

    case (DateTickUnit.DAY):
      years = calendar.get(Calendar.YEAR);
      months = calendar.get(Calendar.MONTH);
      if (this.tickMarkPosition == DateTick.MarkPosition.START) {
        hours = 0;
        minutes = 0;
        seconds = 0;
      } else if (this.tickMarkPosition == DateTick.MarkPosition.MIDDLE) {
        hours = 12;
        minutes = 0;
        seconds = 0;
      } else {
        hours = 23;
        minutes = 59;
        seconds = 59;
      }
      calendar.clear(Calendar.MILLISECOND);
      calendar.set(years, months, value, hours, 0, 0);
      // long result = calendar.getTimeInMillis();
      // won't work with JDK 1.3
      final long result = calendar.getTime().getTime();
      if (result > date.getTime()) {
        calendar.set(years, months, value - 1, hours, 0, 0);
      }
      return calendar.getTime();

    case (DateTickUnit.MONTH):
      years = calendar.get(Calendar.YEAR);
      calendar.clear(Calendar.MILLISECOND);
      calendar.set(years, value, 1, 0, 0, 0);
      Month month = new Month(calendar.getTime());
      Date standardDate = this.calculateDateForPosition(month,
          this.tickMarkPosition);
      final long millis = standardDate.getTime();
      if (millis > date.getTime()) {
        month = (Month) month.previous();
        standardDate = this.calculateDateForPosition(month,
            this.tickMarkPosition);
      }
      return standardDate;

    case (DateTickUnit.YEAR):
      if (this.tickMarkPosition == DateTick.MarkPosition.START) {
        months = 0;
        days = 1;
      } else if (this.tickMarkPosition == DateTick.MarkPosition.MIDDLE) {
        months = 6;
        days = 1;
      } else {
        months = 11;
        days = 31;
      }
      calendar.clear(Calendar.MILLISECOND);
      calendar.set(value, months, days, 0, 0, 0);
      return calendar.getTime();

    default:
      return null;

    }

  }

  /**
   * Calculates the positions of the tick labels for the axis, storing the
   * results in the tick label list (ready for drawing).
   *
   * @param g2
   *            the graphics device.
   * @param state
   *            the axis state.
   * @param dataArea
   *            the area in which the plot should be drawn.
   * @param edge
   *            the location of the axis.
   *
   * @return A list of ticks.
   */
  public List refreshTicks(final GC g2, final AxisState state,
      final Rectangle dataArea, final RectangleEdge edge) {

    List result = null;
    if (edge.isTopOrBottom()) {
      result = this.refreshTicksHorizontal(g2, dataArea, edge);
    } else if (edge.isLeftOrRight()) {
      result = this.refreshTicksVertical(g2, dataArea, edge);
    }
    return result;

  }

  /**
   * Recalculates the ticks for the date axis.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area in which the data is to be drawn.
   * @param edge
   *            the location of the axis.
   *
   * @return A list of ticks.
   */
  protected List refreshTicksHorizontal(final GC g2,
      final Rectangle dataArea, final RectangleEdge edge) {

    final List result = new java.util.ArrayList();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);

    if (this.isAutoTickUnitSelection()) {
      this.selectAutoTickUnit(g2, dataArea, edge);
    }

    final DateTickUnit unit = this.getTickUnit();
    Date tickDate = this.calculateLowestVisibleTickValue(unit);
    final Date upperDate = this.getMaximumDate();
    // float lastX = Float.MIN_VALUE;
    String previousTickLabel = "";
    DateFormat formatter = this.getDateFormatOverride();
    if ((formatter != null) && this.hasDuplicates()) {
      previousTickLabel = formatter.format(tickDate);
    }
    while (tickDate.before(upperDate)) {

      if (!this.isHiddenValue(tickDate.getTime())) {
        // work out the value, label and position
        String tickLabel;
        formatter = this.getDateFormatOverride();
        if (formatter != null) {
          tickLabel = formatter.format(tickDate);
        } else {
          tickLabel = this.tickUnit.dateToString(tickDate);
        }

        // TODO: Code below fixes duplicated labels problem
        if (tickLabel.equals(previousTickLabel)) {
          tickLabel = "";
        } else {
          previousTickLabel = tickLabel;
        }
        TextAnchor anchor = null;
        TextAnchor rotationAnchor = null;
        double angle = 0.0;
        if (this.isVerticalTickLabels()) {
          anchor = TextAnchor.CENTER_RIGHT;
          rotationAnchor = TextAnchor.CENTER_RIGHT;
          if (edge == RectangleEdge.TOP) {
            angle = Math.PI / 2.0;
          } else {
            angle = -Math.PI / 2.0;
          }
        } else {
          if (edge == RectangleEdge.TOP) {
            anchor = TextAnchor.BOTTOM_CENTER;
            rotationAnchor = TextAnchor.BOTTOM_CENTER;
          } else {
            anchor = TextAnchor.TOP_CENTER;
            rotationAnchor = TextAnchor.TOP_CENTER;
          }
        }

        final ITick tick = new DateTick(tickDate, tickLabel, anchor,
            rotationAnchor, angle);
        result.add(tick);
        tickDate = unit.addToDate(tickDate);
      } else {
        tickDate = unit.rollDate(tickDate);
        continue;
      }

      // could add a flag to make the following correction optional...
      switch (unit.getUnit()) {

      case (DateTickUnit.MILLISECOND):
      case (DateTickUnit.SECOND):
      case (DateTickUnit.MINUTE):
      case (DateTickUnit.HOUR):
      case (DateTickUnit.DAY):
        break;
      case (DateTickUnit.MONTH):
        tickDate = this.calculateDateForPosition(new Month(tickDate),
            this.tickMarkPosition);
        break;
      case (DateTickUnit.YEAR):
        tickDate = this.calculateDateForPosition(new Year(tickDate),
            this.tickMarkPosition);
        break;

      default:
        break;

      }

    }
    return result;

  }

  /**
   * Recalculates the ticks for the date axis.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area in which the plot should be drawn.
   * @param edge
   *            the location of the axis.
   *
   * @return A list of ticks.
   */
  protected List refreshTicksVertical(final GC g2, final Rectangle dataArea,
      final RectangleEdge edge) {

    final List result = new java.util.ArrayList();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);

    if (this.isAutoTickUnitSelection()) {
      this.selectAutoTickUnit(g2, dataArea, edge);
    }
    final DateTickUnit unit = this.getTickUnit();
    Date tickDate = this.calculateLowestVisibleTickValue(unit);
    // Date upperDate = calculateHighestVisibleTickValue(unit);
    final Date upperDate = this.getMaximumDate();
    while (tickDate.before(upperDate)) {

      if (!this.isHiddenValue(tickDate.getTime())) {
        // work out the value, label and position
        String tickLabel;
        final DateFormat formatter = this.getDateFormatOverride();
        if (formatter != null) {
          tickLabel = formatter.format(tickDate);
        } else {
          tickLabel = this.tickUnit.dateToString(tickDate);
        }
        TextAnchor anchor = null;
        TextAnchor rotationAnchor = null;
        double angle = 0.0;
        if (this.isVerticalTickLabels()) {
          anchor = TextAnchor.BOTTOM_CENTER;
          rotationAnchor = TextAnchor.BOTTOM_CENTER;
          if (edge == RectangleEdge.LEFT) {
            angle = -Math.PI / 2.0;
          } else {
            angle = Math.PI / 2.0;
          }
        } else {
          if (edge == RectangleEdge.LEFT) {
            anchor = TextAnchor.CENTER_RIGHT;
            rotationAnchor = TextAnchor.CENTER_RIGHT;
          } else {
            anchor = TextAnchor.CENTER_LEFT;
            rotationAnchor = TextAnchor.CENTER_LEFT;
          }
        }

        final ITick tick = new DateTick(tickDate, tickLabel, anchor,
            rotationAnchor, angle);
        result.add(tick);
        tickDate = unit.addToDate(tickDate);
      } else {
        tickDate = unit.rollDate(tickDate);
      }
    }
    return result;
  }

  /**
   * Selects an appropriate tick value for the axis. The strategy is to
   * display as many ticks as possible (selected from an array of 'standard'
   * tick units) without the labels overlapping.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area defined by the axes.
   * @param edge
   *            the axis location.
   */
  protected void selectAutoTickUnit(final GC g2, final Rectangle dataArea,
      final RectangleEdge edge) {

    if (edge.isTopOrBottom()) {
      this.selectHorizontalAutoTickUnit(g2, dataArea, edge);
    } else if (edge.isLeftOrRight()) {
      this.selectVerticalAutoTickUnit(g2, dataArea, edge);
    }

  }

  /**
   * Selects an appropriate tick size for the axis. The strategy is to display
   * as many ticks as possible (selected from a collection of 'standard' tick
   * units) without the labels overlapping.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area defined by the axes.
   * @param edge
   *            the axis location.
   */
  protected void selectHorizontalAutoTickUnit(final GC g2,
      final Rectangle dataArea, final RectangleEdge edge) {

    long shift = 0;
    if (this.timeline instanceof SegmentedTimeline) {
      shift = ((SegmentedTimeline) this.timeline).getStartTime();
    }
    final double zero = this.valueToJava2D(shift + 0.0, dataArea, edge);
    double tickLabelWidth = this.estimateMaximumTickLabelWidth(g2, this
        .getTickUnit());

    // start with the current tick unit...
    final TickUnitSource tickUnits = this.getStandardTickUnits();
    final TickUnit unit1 = tickUnits.getCeilingTickUnit(this.getTickUnit());
    final double x1 = this.valueToJava2D(shift + unit1.getSize(), dataArea,
        edge);
    final double unit1Width = Math.abs(x1 - zero);

    // then extrapolate...
    final double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
    DateTickUnit unit2 = (DateTickUnit) tickUnits.getCeilingTickUnit(guess);
    final double x2 = this.valueToJava2D(shift + unit2.getSize(), dataArea,
        edge);
    final double unit2Width = Math.abs(x2 - zero);
    tickLabelWidth = this.estimateMaximumTickLabelWidth(g2, unit2);
    if (tickLabelWidth > unit2Width) {
      unit2 = (DateTickUnit) tickUnits.getLargerTickUnit(unit2);
    }
    this.setTickUnit(unit2, false, false);
  }

  /**
   * Selects an appropriate tick size for the axis. The strategy is to display
   * as many ticks as possible (selected from a collection of 'standard' tick
   * units) without the labels overlapping.
   *
   * @param g2
   *            the graphics device.
   * @param dataArea
   *            the area in which the plot should be drawn.
   * @param edge
   *            the axis location.
   */
  protected void selectVerticalAutoTickUnit(final GC g2,
      final Rectangle dataArea, final RectangleEdge edge) {

    // start with the current tick unit...
    final TickUnitSource tickUnits = this.getStandardTickUnits();
    final double zero = this.valueToJava2D(0.0, dataArea, edge);

    // start with a unit that is at least 1/10th of the axis length
    final double estimate1 = this.getRange().getLength() / 10.0;
    final DateTickUnit candidate1 = (DateTickUnit) tickUnits
        .getCeilingTickUnit(estimate1);
    final double labelHeight1 = this.estimateMaximumTickLabelHeight(g2,
        candidate1);
    final double y1 = this.valueToJava2D(candidate1.getSize(), dataArea,
        edge);
    final double candidate1UnitHeight = Math.abs(y1 - zero);

    // now extrapolate based on label height and unit height...
    final double estimate2 = (labelHeight1 / candidate1UnitHeight)
        * candidate1.getSize();
    final DateTickUnit candidate2 = (DateTickUnit) tickUnits
        .getCeilingTickUnit(estimate2);
    final double labelHeight2 = this.estimateMaximumTickLabelHeight(g2,
        candidate2);
    final double y2 = this.valueToJava2D(candidate2.getSize(), dataArea,
        edge);
    final double unit2Height = Math.abs(y2 - zero);

    // make final selection...
    DateTickUnit finalUnit;
    if (labelHeight2 < unit2Height) {
      finalUnit = candidate2;
    } else {
      finalUnit = (DateTickUnit) tickUnits.getLargerTickUnit(candidate2);
    }
    this.setTickUnit(finalUnit, false, false);

  }

  /**
   * Sets the date format override. If this is non-null, then it will be used
   * to format the dates on the axis.
   *
   * @param formatter
   *            the date formatter (<code>null</code> permitted).
   */
  public void setDateFormatOverride(final DateFormat formatter) {
    this.dateFormatOverride = formatter;
    this.notifyListeners(new AxisChangeEvent(this));
  }

  /**
   * Sets the maximum date visible on the axis. An {@link AxisChangeEvent} is
   * sent to all registered listeners.
   *
   * @param maximumDate
   *            the date (<code>null</code> not permitted).
   */
  public void setMaximumDate(final Date maximumDate) {
    this.setRange(new DateRange(this.getMinimumDate(), maximumDate), true,
        false);
    this.notifyListeners(new AxisChangeEvent(this));
  }

  /**
   * Sets the minimum date visible on the axis and sends an
   * {@link AxisChangeEvent} to all registered listeners.
   *
   * @param date
   *            the date (<code>null</code> not permitted).
   */
  public void setMinimumDate(final Date date) {
    this.setRange(new DateRange(date, this.getMaximumDate()), true, false);
    this.notifyListeners(new AxisChangeEvent(this));
  }

  /**
   * Sets the axis range and sends an {@link AxisChangeEvent} to all
   * registered listeners.
   *
   * @param lower
   *            the lower bound for the axis.
   * @param upper
   *            the upper bound for the axis.
   */
  public void setRange(final Date lower, final Date upper) {
    if (lower.getTime() >= upper.getTime()) {
      throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
    }
    this.setRange(new DateRange(lower, upper));
  }

  /**
   * Sets the axis range and sends an {@link AxisChangeEvent} to all
   * registered listeners.
   *
   * @param lower
   *            the lower bound for the axis.
   * @param upper
   *            the upper bound for the axis.
   */
  public void setRange(final double lower, final double upper) {
    if (lower >= upper) {
      throw new IllegalArgumentException("Requires 'lower' < 'upper'.");
    }
    this.setRange(new DateRange(lower, upper));
  }

  /**
   * Sets the upper and lower bounds for the axis and sends an
   * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
   * the auto-range flag is set to false.
   *
   * @param range
   *            the new range (<code>null</code> not permitted).
   */
  public void setRange(final Range range) {
    this.setRange(range, true, true);
  }

  /**
   * Sets the range for the axis, if requested, sends an
   * {@link AxisChangeEvent} to all registered listeners. As a side-effect,
   * the auto-range flag is set to <code>false</code> (optional).
   *
   * @param range
   *            the range (<code>null</code> not permitted).
   * @param turnOffAutoRange
   *            a flag that controls whether or not the auto range is turned
   *            off.
   * @param notify
   *            a flag that controls whether or not listeners are notified.
   */
  public void setRange(Range range, final boolean turnOffAutoRange,
      final boolean notify) {
    if (range == null) {
      throw new IllegalArgumentException("Null 'range' argument.");
    }
    // usually the range will be a DateRange, but if it isn't do a
    // conversion...
    if (!(range instanceof DateRange)) {
      range = new DateRange(range);
    }
    super.setRange(range, turnOffAutoRange, notify);
  }

  /**
   * Sets the tick mark position (start, middle or end of the time period) and
   * sends an {@link AxisChangeEvent} to all registered listeners.
   *
   * @param position
   *            the position (<code>null</code> not permitted).
   */
  public void setTickMarkPosition(final DateTick.MarkPosition position) {
    if (position == null) {
      throw new IllegalArgumentException("Null 'position' argument.");
    }
    this.tickMarkPosition = position;
    this.notifyListeners(new AxisChangeEvent(this));
  }

  /**
   * Sets the tick unit for the axis. The auto-tick-unit-selection flag is set
   * to <code>false</code>, and registered listeners are notified that the
   * axis has been changed.
   *
   * @param unit
   *            the tick unit.
   */
  public void setTickUnit(final DateTickUnit unit) {
    this.setTickUnit(unit, true, true);
  }

  /**
   * Sets the tick unit attribute without any other side effects.
   *
   * @param unit
   *            the new tick unit.
   * @param notify
   *            notify registered listeners?
   * @param turnOffAutoSelection
   *            turn off auto selection?
   */
  public void setTickUnit(final DateTickUnit unit, final boolean notify,
      final boolean turnOffAutoSelection) {

    this.tickUnit = unit;
    if (turnOffAutoSelection) {
      this.setAutoTickUnitSelection(false, false);
    }
    if (notify) {
      this.notifyListeners(new AxisChangeEvent(this));
    }

  }

  /**
   * Sets the underlying timeline to use for this axis.
   * <P>
   * If the timeline is changed, an {@link AxisChangeEvent} is sent to all
   * registered listeners.
   *
   * @param timeline
   *            the timeline.
   */
  public void setTimeline(final Timeline timeline) {
    if (this.timeline != timeline) {
      this.timeline = timeline;
      this.notifyListeners(new AxisChangeEvent(this));
    }
  }

  /**
   * Translates the data value to the display coordinates (Java 2D User Space)
   * of the chart.
   *
   * @param value
   *            the date to be plotted.
   * @param area
   *            the rectangle (in Java2D space) where the data is to be
   *            plotted.
   * @param edge
   *            the axis location.
   *
   * @return The coordinate corresponding to the supplied data value.
   */
  public double valueToJava2D(double value, final Rectangle area,
      final RectangleEdge edge) {

    value = this.timeline.toTimelineValue((long) value);

    final DateRange range = (DateRange) this.getRange();
    final double axisMin = this.timeline.toTimelineValue(range
        .getLowerDate());
    final double axisMax = this.timeline.toTimelineValue(range
        .getUpperDate());
    double result = 0.0;
    if (edge.isTopOrBottom()) {
      final double minX = RectangleUtil.getMinX(area);
      final double maxX = RectangleUtil.getMaxX(area);
      if (this.isInverted()) {
        result = maxX + ((value - axisMin) / (axisMax - axisMin))
            * (minX - maxX);
      } else {
        result = minX + ((value - axisMin) / (axisMax - axisMin))
            * (maxX - minX);
      }
    } else if (edge.isLeftOrRight()) {
      final double minY = RectangleUtil.getMinY(area);
      final double maxY = RectangleUtil.getMaxY(area);
      if (this.isInverted()) {
        result = minY
            + (((value - axisMin) / (axisMax - axisMin)) * (maxY - minY));
      } else {
        result = maxY
            - (((value - axisMin) / (axisMax - axisMin)) * (maxY - minY));
      }
    }
    return result;

  }

  /**
   * Zooms in on the current range.
   *
   * @param lowerPercent
   *            the new lower bound.
   * @param upperPercent
   *            the new upper bound.
   */
  public void zoomRange(final double lowerPercent, final double upperPercent) {
    final double start = this.timeline.toTimelineValue((long) this
        .getRange().getLowerBound());
    final double length = (this.timeline.toTimelineValue((long) this
        .getRange().getUpperBound()) - this.timeline
        .toTimelineValue((long) this.getRange().getLowerBound()));
    Range adjusted = null;
    if (this.isInverted()) {
      adjusted = new DateRange(
          this.timeline
              .toMillisecond((long) (start + (length * (1 - upperPercent)))),
          this.timeline
              .toMillisecond((long) (start + (length * (1 - lowerPercent)))));
    } else {
      adjusted = new DateRange(this.timeline
          .toMillisecond((long) (start + length * lowerPercent)),
          this.timeline.toMillisecond((long) (start + length
              * upperPercent)));
    }
    this.setRange(adjusted);
  }

}
 
TOP

Related Classes of com.positive.charts.axis.DateAxis$DefaultTimeline

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.