Package com.positive.charts.axis

Source Code of com.positive.charts.axis.NumberAxis$RangeType

package com.positive.charts.axis;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

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

import com.positive.charts.axis.ticks.NumberTick;
import com.positive.charts.axis.ticks.NumberTickUnit;
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.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;

/**
* An axis for displaying numerical data.
* <P>
* If the axis is set up to automatically determine its range to fit the data,
* you can ensure that the range includes zero (statisticians usually prefer
* this) by setting the <code>autoRangeIncludesZero</code> flag to
* <code>true</code>.
* <P>
* The <code>NumberAxis</code> class has a mechanism for automatically selecting
* a tick unit that is appropriate for the current axis range. This mechanism is
* an adaptation of code suggested by Laurence Vanhelsuwe.
*/
public class NumberAxis extends ValueAxis {

  public static class RangeType {
    public static final RangeType FULL = new RangeType();
    public static final RangeType POSITIVE = new RangeType();
    public static final RangeType NEGATIVE = new RangeType();
  }

  /** For serialization. */
  private static final long serialVersionUID = 2805933088476185789L;

  /** The default value for the autoRangeIncludesZero flag. */
  public static final boolean DEFAULT_AUTO_RANGE_INCLUDES_ZERO = true;

  /** The default value for the autoRangeStickyZero flag. */
  public static final boolean DEFAULT_AUTO_RANGE_STICKY_ZERO = true;

  /** The default tick unit. */
  public static final NumberTickUnit DEFAULT_TICK_UNIT = new NumberTickUnit(
      1.0, new DecimalFormat("0"));

  /** The default setting for the vertical tick labels flag. */
  public static final boolean DEFAULT_VERTICAL_TICK_LABELS = false;

  /**
   * Returns a collection of tick units for integer values.
   *
   * @return A collection of tick units for integer values.
   */
  public static TickUnitSource createIntegerTickUnits() {
    final TickUnits units = new TickUnits();
    final DecimalFormat df0 = new DecimalFormat("0");
    final DecimalFormat df1 = new DecimalFormat("#,##0");
    units.add(new NumberTickUnit(1, df0));
    units.add(new NumberTickUnit(2, df0));
    units.add(new NumberTickUnit(5, df0));
    units.add(new NumberTickUnit(10, df0));
    units.add(new NumberTickUnit(20, df0));
    units.add(new NumberTickUnit(50, df0));
    units.add(new NumberTickUnit(100, df0));
    units.add(new NumberTickUnit(200, df0));
    units.add(new NumberTickUnit(500, df0));
    units.add(new NumberTickUnit(1000, df1));
    units.add(new NumberTickUnit(2000, df1));
    units.add(new NumberTickUnit(5000, df1));
    units.add(new NumberTickUnit(10000, df1));
    units.add(new NumberTickUnit(20000, df1));
    units.add(new NumberTickUnit(50000, df1));
    units.add(new NumberTickUnit(100000, df1));
    units.add(new NumberTickUnit(200000, df1));
    units.add(new NumberTickUnit(500000, df1));
    units.add(new NumberTickUnit(1000000, df1));
    units.add(new NumberTickUnit(2000000, df1));
    units.add(new NumberTickUnit(5000000, df1));
    units.add(new NumberTickUnit(10000000, df1));
    units.add(new NumberTickUnit(20000000, df1));
    units.add(new NumberTickUnit(50000000, df1));
    units.add(new NumberTickUnit(100000000, df1));
    units.add(new NumberTickUnit(200000000, df1));
    units.add(new NumberTickUnit(500000000, df1));
    units.add(new NumberTickUnit(1000000000, df1));
    units.add(new NumberTickUnit(2000000000, df1));
    units.add(new NumberTickUnit(5000000000.0, df1));
    units.add(new NumberTickUnit(10000000000.0, df1));

    return units;
  }

  /**
   * Returns a collection of tick units for integer values. Uses a given
   * Locale to create the DecimalFormats.
   *
   * @param locale
   *            the locale to use to represent Numbers.
   *
   * @return A collection of tick units for integer values.
   */
  public static TickUnitSource createIntegerTickUnits(final Locale locale) {
    final TickUnits units = new TickUnits();

    final NumberFormat numberFormat = NumberFormat
        .getNumberInstance(locale);

    units.add(new NumberTickUnit(1, numberFormat));
    units.add(new NumberTickUnit(2, numberFormat));
    units.add(new NumberTickUnit(5, numberFormat));
    units.add(new NumberTickUnit(10, numberFormat));
    units.add(new NumberTickUnit(20, numberFormat));
    units.add(new NumberTickUnit(50, numberFormat));
    units.add(new NumberTickUnit(100, numberFormat));
    units.add(new NumberTickUnit(200, numberFormat));
    units.add(new NumberTickUnit(500, numberFormat));
    units.add(new NumberTickUnit(1000, numberFormat));
    units.add(new NumberTickUnit(2000, numberFormat));
    units.add(new NumberTickUnit(5000, numberFormat));
    units.add(new NumberTickUnit(10000, numberFormat));
    units.add(new NumberTickUnit(20000, numberFormat));
    units.add(new NumberTickUnit(50000, numberFormat));
    units.add(new NumberTickUnit(100000, numberFormat));
    units.add(new NumberTickUnit(200000, numberFormat));
    units.add(new NumberTickUnit(500000, numberFormat));
    units.add(new NumberTickUnit(1000000, numberFormat));
    units.add(new NumberTickUnit(2000000, numberFormat));
    units.add(new NumberTickUnit(5000000, numberFormat));
    units.add(new NumberTickUnit(10000000, numberFormat));
    units.add(new NumberTickUnit(20000000, numberFormat));
    units.add(new NumberTickUnit(50000000, numberFormat));
    units.add(new NumberTickUnit(100000000, numberFormat));
    units.add(new NumberTickUnit(200000000, numberFormat));
    units.add(new NumberTickUnit(500000000, numberFormat));
    units.add(new NumberTickUnit(1000000000, numberFormat));
    units.add(new NumberTickUnit(2000000000, numberFormat));
    units.add(new NumberTickUnit(5000000000.0, numberFormat));
    units.add(new NumberTickUnit(10000000000.0, numberFormat));

    return units;
  }

  /**
   * Creates the standard tick units.
   * <P>
   * If you don't like these defaults, create your own instance of TickUnits
   * and then pass it to the setStandardTickUnits() method in the NumberAxis
   * class.
   *
   * @return The standard tick units.
   */
  public static TickUnitSource createStandardTickUnits() {
    final TickUnits units = new TickUnits();
    final DecimalFormat df0 = new DecimalFormat("0.00000000");
    final DecimalFormat df1 = new DecimalFormat("0.0000000");
    final DecimalFormat df2 = new DecimalFormat("0.000000");
    final DecimalFormat df3 = new DecimalFormat("0.00000");
    final DecimalFormat df4 = new DecimalFormat("0.0000");
    final DecimalFormat df5 = new DecimalFormat("0.000");
    final DecimalFormat df6 = new DecimalFormat("0.00");
    final DecimalFormat df7 = new DecimalFormat("0.0");
    final DecimalFormat df8 = new DecimalFormat("#,##0");
    final DecimalFormat df9 = new DecimalFormat("#,###,##0");
    final DecimalFormat df10 = new DecimalFormat("#,###,###,##0");

    // we can add the units in any order, the TickUnits collection will
    // sort them...
    units.add(new NumberTickUnit(0.0000001, df1));
    units.add(new NumberTickUnit(0.000001, df2));
    units.add(new NumberTickUnit(0.00001, df3));
    units.add(new NumberTickUnit(0.0001, df4));
    units.add(new NumberTickUnit(0.001, df5));
    units.add(new NumberTickUnit(0.01, df6));
    units.add(new NumberTickUnit(0.1, df7));
    units.add(new NumberTickUnit(1, df8));
    units.add(new NumberTickUnit(10, df8));
    units.add(new NumberTickUnit(100, df8));
    units.add(new NumberTickUnit(1000, df8));
    units.add(new NumberTickUnit(10000, df8));
    units.add(new NumberTickUnit(100000, df8));
    units.add(new NumberTickUnit(1000000, df9));
    units.add(new NumberTickUnit(10000000, df9));
    units.add(new NumberTickUnit(100000000, df9));
    units.add(new NumberTickUnit(1000000000, df10));
    units.add(new NumberTickUnit(10000000000.0, df10));
    units.add(new NumberTickUnit(100000000000.0, df10));

    units.add(new NumberTickUnit(0.00000025, df0));
    units.add(new NumberTickUnit(0.0000025, df1));
    units.add(new NumberTickUnit(0.000025, df2));
    units.add(new NumberTickUnit(0.00025, df3));
    units.add(new NumberTickUnit(0.0025, df4));
    units.add(new NumberTickUnit(0.025, df5));
    units.add(new NumberTickUnit(0.25, df6));
    units.add(new NumberTickUnit(2.5, df7));
    units.add(new NumberTickUnit(25, df8));
    units.add(new NumberTickUnit(250, df8));
    units.add(new NumberTickUnit(2500, df8));
    units.add(new NumberTickUnit(25000, df8));
    units.add(new NumberTickUnit(250000, df8));
    units.add(new NumberTickUnit(2500000, df9));
    units.add(new NumberTickUnit(25000000, df9));
    units.add(new NumberTickUnit(250000000, df9));
    units.add(new NumberTickUnit(2500000000.0, df10));
    units.add(new NumberTickUnit(25000000000.0, df10));
    units.add(new NumberTickUnit(250000000000.0, df10));

    units.add(new NumberTickUnit(0.0000005, df1));
    units.add(new NumberTickUnit(0.000005, df2));
    units.add(new NumberTickUnit(0.00005, df3));
    units.add(new NumberTickUnit(0.0005, df4));
    units.add(new NumberTickUnit(0.005, df5));
    units.add(new NumberTickUnit(0.05, df6));
    units.add(new NumberTickUnit(0.5, df7));
    units.add(new NumberTickUnit(5L, df8));
    units.add(new NumberTickUnit(50L, df8));
    units.add(new NumberTickUnit(500L, df8));
    units.add(new NumberTickUnit(5000L, df8));
    units.add(new NumberTickUnit(50000L, df8));
    units.add(new NumberTickUnit(500000L, df8));
    units.add(new NumberTickUnit(5000000L, df9));
    units.add(new NumberTickUnit(50000000L, df9));
    units.add(new NumberTickUnit(500000000L, df9));
    units.add(new NumberTickUnit(5000000000L, df10));
    units.add(new NumberTickUnit(50000000000L, df10));
    units.add(new NumberTickUnit(500000000000L, df10));

    return units;
  }

  /**
   * Creates a collection of standard tick units. The supplied locale is used
   * to create the number formatter (a localised instance of
   * <code>NumberFormat</code>).
   * <P>
   * If you don't like these defaults, create your own instance of
   * {@link TickUnits} and then pass it to the
   * <code>setStandardTickUnits()</code> method.
   *
   * @param locale
   *            the locale.
   *
   * @return A tick unit collection.
   */
  public static TickUnitSource createStandardTickUnits(final Locale locale) {
    final TickUnits units = new TickUnits();

    final NumberFormat numberFormat = NumberFormat
        .getNumberInstance(locale);

    // we can add the units in any order, the TickUnits collection will
    // sort them...
    units.add(new NumberTickUnit(0.0000001, numberFormat));
    units.add(new NumberTickUnit(0.000001, numberFormat));
    units.add(new NumberTickUnit(0.00001, numberFormat));
    units.add(new NumberTickUnit(0.0001, numberFormat));
    units.add(new NumberTickUnit(0.001, numberFormat));
    units.add(new NumberTickUnit(0.01, numberFormat));
    units.add(new NumberTickUnit(0.1, numberFormat));
    units.add(new NumberTickUnit(1, numberFormat));
    units.add(new NumberTickUnit(10, numberFormat));
    units.add(new NumberTickUnit(100, numberFormat));
    units.add(new NumberTickUnit(1000, numberFormat));
    units.add(new NumberTickUnit(10000, numberFormat));
    units.add(new NumberTickUnit(100000, numberFormat));
    units.add(new NumberTickUnit(1000000, numberFormat));
    units.add(new NumberTickUnit(10000000, numberFormat));
    units.add(new NumberTickUnit(100000000, numberFormat));
    units.add(new NumberTickUnit(1000000000, numberFormat));
    units.add(new NumberTickUnit(10000000000.0, numberFormat));

    units.add(new NumberTickUnit(0.00000025, numberFormat));
    units.add(new NumberTickUnit(0.0000025, numberFormat));
    units.add(new NumberTickUnit(0.000025, numberFormat));
    units.add(new NumberTickUnit(0.00025, numberFormat));
    units.add(new NumberTickUnit(0.0025, numberFormat));
    units.add(new NumberTickUnit(0.025, numberFormat));
    units.add(new NumberTickUnit(0.25, numberFormat));
    units.add(new NumberTickUnit(2.5, numberFormat));
    units.add(new NumberTickUnit(25, numberFormat));
    units.add(new NumberTickUnit(250, numberFormat));
    units.add(new NumberTickUnit(2500, numberFormat));
    units.add(new NumberTickUnit(25000, numberFormat));
    units.add(new NumberTickUnit(250000, numberFormat));
    units.add(new NumberTickUnit(2500000, numberFormat));
    units.add(new NumberTickUnit(25000000, numberFormat));
    units.add(new NumberTickUnit(250000000, numberFormat));
    units.add(new NumberTickUnit(2500000000.0, numberFormat));
    units.add(new NumberTickUnit(25000000000.0, numberFormat));

    units.add(new NumberTickUnit(0.0000005, numberFormat));
    units.add(new NumberTickUnit(0.000005, numberFormat));
    units.add(new NumberTickUnit(0.00005, numberFormat));
    units.add(new NumberTickUnit(0.0005, numberFormat));
    units.add(new NumberTickUnit(0.005, numberFormat));
    units.add(new NumberTickUnit(0.05, numberFormat));
    units.add(new NumberTickUnit(0.5, numberFormat));
    units.add(new NumberTickUnit(5L, numberFormat));
    units.add(new NumberTickUnit(50L, numberFormat));
    units.add(new NumberTickUnit(500L, numberFormat));
    units.add(new NumberTickUnit(5000L, numberFormat));
    units.add(new NumberTickUnit(50000L, numberFormat));
    units.add(new NumberTickUnit(500000L, numberFormat));
    units.add(new NumberTickUnit(5000000L, numberFormat));
    units.add(new NumberTickUnit(50000000L, numberFormat));
    units.add(new NumberTickUnit(500000000L, numberFormat));
    units.add(new NumberTickUnit(5000000000L, numberFormat));
    units.add(new NumberTickUnit(50000000000L, numberFormat));

    return units;
  }

  /**
   * The range type (can be used to force the axis to display only positive
   * values or only negative values.
   */
  private RangeType rangeType;

  /**
   * A flag that affects the axis range when the range is determined
   * automatically. If the auto range does NOT include zero and this flag is
   * TRUE, then the range is changed to include zero.
   */
  private boolean autoRangeIncludesZero;

  /**
   * A flag that affects the size of the margins added to the axis range when
   * the range is determined automatically. If the value 0 falls within the
   * margin and this flag is TRUE, then the margin is truncated at zero.
   */
  private boolean autoRangeStickyZero;

  /** The tick unit for the axis. */
  private NumberTickUnit tickUnit;

  /** The override number format. */
  private NumberFormat numberFormatOverride;

  /**
   * Default constructor.
   */
  public NumberAxis() {
    this(null);
  }

  /**
   * Constructs a number axis, using default values where necessary.
   *
   * @param label
   *            the axis label (<code>null</code> permitted).
   */
  public NumberAxis(final String label) {
    super(label, NumberAxis.createStandardTickUnits());
    this.rangeType = RangeType.FULL;
    this.autoRangeIncludesZero = DEFAULT_AUTO_RANGE_INCLUDES_ZERO;
    this.autoRangeStickyZero = DEFAULT_AUTO_RANGE_STICKY_ZERO;
    this.tickUnit = DEFAULT_TICK_UNIT;
    this.numberFormatOverride = null;
  }

  /**
   * 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) {
        r = new Range(DEFAULT_LOWER_BOUND, DEFAULT_UPPER_BOUND);
      }

      double upper = r.getUpperBound();
      double lower = r.getLowerBound();
      if (this.rangeType == RangeType.POSITIVE) {
        lower = Math.max(0.0, lower);
        upper = Math.max(0.0, upper);
      } else if (this.rangeType == RangeType.NEGATIVE) {
        lower = Math.min(0.0, lower);
        upper = Math.min(0.0, upper);
      }

      if (this.getAutoRangeIncludesZero()) {
        lower = Math.min(lower, 0.0);
        upper = Math.max(upper, 0.0);
      }
      final double range = upper - lower;

      // if fixed auto range, then derive lower bound...
      final double fixedAutoRange = this.getFixedAutoRange();
      if (fixedAutoRange > 0.0) {
        lower = upper - fixedAutoRange;
      } else {
        // ensure the autorange is at least <minRange> in size...
        final double minRange = this.getAutoRangeMinimumSize();
        if (range < minRange) {
          final double expand = (minRange - range) / 2;
          upper = upper + expand;
          lower = lower - expand;
          if (lower == upper) { // see bug report 1549218
            final double adjust = Math.abs(lower) / 10.0;
            lower = lower - adjust;
            upper = upper + adjust;
          }
          if (this.rangeType == RangeType.POSITIVE) {
            if (lower < 0.0) {
              upper = upper - lower;
              lower = 0.0;
            }
          } else if (this.rangeType == RangeType.NEGATIVE) {
            if (upper > 0.0) {
              lower = lower - upper;
              upper = 0.0;
            }
          }
        }

        if (this.getAutoRangeStickyZero()) {
          if (upper <= 0.0) {
            upper = Math.min(0.0, upper + this.getUpperMargin()
                * range);
          } else {
            upper = upper + this.getUpperMargin() * range;
          }
          if (lower >= 0.0) {
            lower = Math.max(0.0, lower - this.getLowerMargin()
                * range);
          } else {
            lower = lower - this.getLowerMargin() * range;
          }
        } else {
          upper = upper + this.getUpperMargin() * range;
          lower = lower - this.getLowerMargin() * range;
        }
      }

      this.setRange(new Range(lower, upper), false, false);
    }

  }

  /**
   * Calculates the value of the highest visible tick on the axis.
   *
   * @return The value of the highest visible tick on the axis.
   */
  protected double calculateHighestVisibleTickValue() {
    final double unit = this.getTickUnit().getSize();
    final double index = Math.floor(this.getRange().getUpperBound() / unit);
    return index * unit;
  }

  /**
   * Calculates the value of the lowest visible tick on the axis.
   *
   * @return The value of the lowest visible tick on the axis.
   */
  protected double calculateLowestVisibleTickValue() {
    final double unit = this.getTickUnit().getSize();
    final double index = Math.ceil(this.getRange().getLowerBound() / unit);
    return index * unit;
  }

  /**
   * Calculates the number of visible ticks.
   *
   * @return The number of visible ticks on the axis.
   */
  protected int calculateVisibleTickCount() {
    final double unit = this.getTickUnit().getSize();
    final Range range = this.getRange();
    return (int) (Math.floor(range.getUpperBound() / unit)
        - Math.ceil(range.getLowerBound() / unit) + 1);
  }

  /**
   * 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();
    }
  }

  /**
   * 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) {
    AxisState state = null;
    // if the axis is not visible, don't draw it...
    if (!this.isVisible()) {
      state = new AxisState(cursor);
      // even though the axis is not visible, we need ticks for the
      // gridlines...
      final List ticks = this.refreshTicks(g2, state, dataArea, edge);
      state.setTicks(ticks);
      return state;
    }

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

    // // draw the marker band (if there is one)...
    // if (getMarkerBand() != null) {
    // if (edge == RectangleEdge.BOTTOM) {
    // cursor = cursor - getMarkerBand().getHeight(g2);
    // }
    // getMarkerBand().draw(g2, plotArea, dataArea, 0, cursor);
    // }

    // draw the axis label...
    state = this.drawLabel(this.getLabel(), g2, plotArea, dataArea, edge,
        state);

    return state;
  }

  /**
   * Estimates the maximum tick label height.
   *
   * @param g2
   *            the graphics device.
   *
   * @return The maximum height.
   */
  protected double estimateMaximumTickLabelHeight(final GC g2) {
    final RectangleInsets tickLabelInsets = this.getTickLabelInsets();
    double result = tickLabelInsets.getTop() + tickLabelInsets.getBottom();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);
    result += g2.getFontMetrics().getHeight();
    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 gc
   *            the graphics device.
   * @param unit
   *            the tick unit to use for calculation.
   *
   * @return The estimated maximum width of the tick labels.
   */
  protected double estimateMaximumTickLabelWidth(final GC gc,
      final TickUnit unit) {
    final RectangleInsets tickLabelInsets = this.getTickLabelInsets();
    double result = tickLabelInsets.getLeft() + tickLabelInsets.getRight();

    if (this.isVerticalTickLabels()) {
      // all tick labels have the same width (equal to the height of the
      // font)...
      result += gc.getFontMetrics().getHeight();
    } else {
      // look at lower and upper bounds...
      final Range range = this.getRange();
      final double lower = range.getLowerBound();
      final double upper = range.getUpperBound();
      final String lowerStr = unit.valueToString(lower);
      final String upperStr = unit.valueToString(upper);
      final double w1 = gc.textExtent(lowerStr).x;
      final double w2 = gc.textExtent(upperStr).x;
      result += Math.max(w1, w2);
    }
    return result;
  }

  /**
   * Returns the flag that indicates whether or not the automatic axis range
   * (if indeed it is determined automatically) is forced to include zero.
   *
   * @return The flag.
   */
  public boolean getAutoRangeIncludesZero() {
    return this.autoRangeIncludesZero;
  }

  /**
   * Returns a flag that affects the auto-range when zero falls outside the
   * data range but inside the margins defined for the axis.
   *
   * @return The flag.
   */
  public boolean getAutoRangeStickyZero() {
    return this.autoRangeStickyZero;
  }

  /**
   * Returns the number format override. If this is non-null, then it will be
   * used to format the numbers on the axis.
   *
   * @return The number formatter (possibly <code>null</code>).
   */
  public NumberFormat getNumberFormatOverride() {
    return this.numberFormatOverride;
  }

  /**
   * Returns the axis range type.
   *
   * @return The axis range type (never <code>null</code>).
   */
  public RangeType getRangeType() {
    return this.rangeType;
  }

  /**
   * Returns the tick unit for the axis.
   * <p>
   * Note: if the <code>autoTickUnitSelection</code> flag is <code>true</code>
   * the tick unit may be changed while the axis is being drawn, so in that
   * case the return value from this method may be irrelevant if the method is
   * called before the axis has been drawn.
   *
   * @return The tick unit for the axis.
   *
   * @see #setTickUnit(NumberTickUnit)
   * @see ValueAxis#isAutoTickUnitSelection()
   */
  public NumberTickUnit getTickUnit() {
    return this.tickUnit;
  }

  /**
   * Converts a coordinate in Java2D space to the corresponding data value,
   * assuming that the axis runs along one edge of the specified dataArea.
   *
   * @param java2DValue
   *            the coordinate in Java2D space.
   * @param area
   *            the area in which the data is plotted.
   * @param edge
   *            the location.
   *
   * @return The data value.
   */
  public double java2DToValue(final double java2DValue, final Rectangle area,
      final RectangleEdge edge) {

    final Range range = this.getRange();
    final double axisMin = range.getLowerBound();
    final double axisMax = range.getUpperBound();

    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;
    }
    if (this.isInverted()) {
      return axisMax - (java2DValue - min) / (max - min)
          * (axisMax - axisMin);
    } else {
      return axisMin + (java2DValue - min) / (max - min)
          * (axisMax - axisMin);
    }

  }

  /**
   * 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) {
    if (edge.isTopOrBottom()) {
      return this.refreshTicksHorizontal(g2, dataArea, edge);
    } else {
      return this.refreshTicksVertical(g2, dataArea, edge);
    }
  }

  /**
   * 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 dataArea
   *            the area in which the data should 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 ArrayList();

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

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

    final double size = this.getTickUnit().getSize();
    final int count = this.calculateVisibleTickCount();
    final double lowestTickValue = this.calculateLowestVisibleTickValue();

    if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
      for (int i = 0; i < count; i++) {
        final double currentTickValue = lowestTickValue + (i * size);
        String tickLabel;
        final NumberFormat formatter = this.getNumberFormatOverride();
        if (formatter != null) {
          tickLabel = formatter.format(currentTickValue);
        } else {
          tickLabel = this.getTickUnit().valueToString(
              currentTickValue);
        }
        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 NumberTick tick = new NumberTick(new Double(
            currentTickValue), tickLabel, anchor, rotationAnchor,
            angle);
        result.add(tick);
      }
    }
    return result;
  }

  /**
   * 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 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 ArrayList();
    result.clear();

    final Font tickLabelFont = this.getTickLabelFont();
    g2.setFont(tickLabelFont);
    if (this.isAutoTickUnitSelection()) {
      this.selectAutoTickUnit(g2, dataArea, edge);
    }

    final double size = this.getTickUnit().getSize();
    final int count = this.calculateVisibleTickCount();
    final double lowestTickValue = this.calculateLowestVisibleTickValue();

    if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
      for (int i = 0; i < count; i++) {
        final double currentTickValue = lowestTickValue + (i * size);
        String tickLabel;
        final NumberFormat formatter = this.getNumberFormatOverride();
        if (formatter != null) {
          tickLabel = formatter.format(currentTickValue);
        } else {
          tickLabel = this.getTickUnit().valueToString(
              currentTickValue);
        }

        TextAnchor anchor = null;
        TextAnchor rotationAnchor = null;
        double angle = 0.0;
        if (this.isVerticalTickLabels()) {
          if (edge == RectangleEdge.LEFT) {
            anchor = TextAnchor.BOTTOM_CENTER;
            rotationAnchor = TextAnchor.BOTTOM_CENTER;
            angle = -Math.PI / 2.0;
          } else {
            anchor = TextAnchor.BOTTOM_CENTER;
            rotationAnchor = TextAnchor.BOTTOM_CENTER;
            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 NumberTick tick = new NumberTick(new Double(
            currentTickValue), tickLabel, anchor, rotationAnchor,
            angle);
        result.add(tick);
      }
    }
    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 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 selectHorizontalAutoTickUnit(final GC g2,
      final Rectangle dataArea, final RectangleEdge 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 unit1Width = this.lengthToJava2D(unit1.getSize(),
        dataArea, edge);

    // then extrapolate...
    final double guess = (tickLabelWidth / unit1Width) * unit1.getSize();

    NumberTickUnit unit2 = (NumberTickUnit) tickUnits
        .getCeilingTickUnit(guess);
    final double unit2Width = this.lengthToJava2D(unit2.getSize(),
        dataArea, edge);

    tickLabelWidth = this.estimateMaximumTickLabelWidth(g2, unit2);
    if (tickLabelWidth > unit2Width) {
      unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
    }

    this.setTickUnit(unit2, false, false);
  }

  /**
   * 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 in which the plot should be drawn.
   * @param edge
   *            the axis location.
   */
  protected void selectVerticalAutoTickUnit(final GC g2,
      final Rectangle dataArea, final RectangleEdge edge) {
    double tickLabelHeight = this.estimateMaximumTickLabelHeight(g2);

    // start with the current tick unit...
    final TickUnitSource tickUnits = this.getStandardTickUnits();
    final TickUnit unit1 = tickUnits.getCeilingTickUnit(this.getTickUnit());
    final double unitHeight = this.lengthToJava2D(unit1.getSize(),
        dataArea, edge);

    // then extrapolate...
    final double guess = (tickLabelHeight / unitHeight) * unit1.getSize();

    NumberTickUnit unit2 = (NumberTickUnit) tickUnits
        .getCeilingTickUnit(guess);
    final double unit2Height = this.lengthToJava2D(unit2.getSize(),
        dataArea, edge);

    tickLabelHeight = this.estimateMaximumTickLabelHeight(g2);
    if (tickLabelHeight > unit2Height) {
      unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
    }

    this.setTickUnit(unit2, false, false);
  }

  /**
   * Sets the flag that indicates whether or not the axis range, if
   * automatically calculated, is forced to include zero.
   * <p>
   * If the flag is changed to <code>true</code>, the axis range is
   * recalculated.
   * <p>
   * Any change to the flag will trigger an {@link AxisChangeEvent}.
   *
   * @param flag
   *            the new value of the flag.
   */
  public void setAutoRangeIncludesZero(final boolean flag) {
    if (this.autoRangeIncludesZero != flag) {
      this.autoRangeIncludesZero = flag;
      if (this.isAutoRange()) {
        this.autoAdjustRange();
      }
      this.notifyListeners(new AxisChangeEvent(this));
    }
  }

  /**
   * Sets a flag that affects the auto-range when zero falls outside the data
   * range but inside the margins defined for the axis.
   *
   * @param flag
   *            the new flag.
   */
  public void setAutoRangeStickyZero(final boolean flag) {
    if (this.autoRangeStickyZero != flag) {
      this.autoRangeStickyZero = flag;
      if (this.isAutoRange()) {
        this.autoAdjustRange();
      }
      this.notifyListeners(new AxisChangeEvent(this));
    }
  }

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

  /**
   * Sets the axis range type.
   *
   * @param rangeType
   *            the range type (<code>null</code> not permitted).
   */
  public void setRangeType(final RangeType rangeType) {
    if (rangeType == null) {
      throw new IllegalArgumentException("Null 'rangeType' argument.");
    }
    this.rangeType = rangeType;
    this.notifyListeners(new AxisChangeEvent(this));
  }

  /**
   * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to
   * all registered listeners. A side effect of calling this method is that
   * the "auto-select" feature for tick units is switched off (you can restore
   * it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
   *
   * @param unit
   *            the new tick unit (<code>null</code> not permitted).
   *
   * @see #getTickUnit()
   * @see #setTickUnit(NumberTickUnit, boolean, boolean)
   */
  public void setTickUnit(final NumberTickUnit unit) {
    // defer argument checking...
    this.setTickUnit(unit, true, true);
  }

  /**
   * Sets the tick unit for the axis and, if requested, sends an
   * {@link AxisChangeEvent} to all registered listeners. In addition, an
   * option is provided to turn off the "auto-select" feature for tick units
   * (you can restore it using the
   * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
   *
   * @param unit
   *            the new tick unit (<code>null</code> not permitted).
   * @param notify
   *            notify listeners?
   * @param turnOffAutoSelect
   *            turn off the auto-tick selection?
   */
  public void setTickUnit(final NumberTickUnit unit, final boolean notify,
      final boolean turnOffAutoSelect) {

    if (unit == null) {
      throw new IllegalArgumentException("Null 'unit' argument.");
    }
    this.tickUnit = unit;
    if (turnOffAutoSelect) {
      this.setAutoTickUnitSelection(false, false);
    }
    if (notify) {
      this.notifyListeners(new AxisChangeEvent(this));
    }

  }

  /**
   * Converts a data value to a coordinate in Java2D space, assuming that the
   * axis runs along one edge of the specified dataArea.
   * <p>
   * Note that it is possible for the coordinate to fall outside the plotArea.
   *
   * @param value
   *            the data value.
   * @param area
   *            the area for plotting the data.
   * @param edge
   *            the axis location.
   *
   * @return The Java2D coordinate.
   */
  public double valueToJava2D(final double value, final Rectangle area,
      final RectangleEdge edge) {
    final Range range = this.getRange();
    final double axisMin = range.getLowerBound();
    final double axisMax = range.getUpperBound();

    double min = 0.0;
    double max = 0.0;
    if (edge.isTopOrBottom()) {
      min = RectangleUtil.getMinX(area);
      max = RectangleUtil.getMaxX(area);
    } else if (edge.isLeftOrRight()) {
      max = RectangleUtil.getMinY(area);
      min = RectangleUtil.getMaxY(area);
    }
    if (this.isInverted()) {
      return max - ((value - axisMin) / (axisMax - axisMin))
          * (max - min);
    } else {
      return min + ((value - axisMin) / (axisMax - axisMin))
          * (max - min);
    }
  }

}
TOP

Related Classes of com.positive.charts.axis.NumberAxis$RangeType

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.