0001: /* ===========================================================
0002: * JFreeChart : a free chart library for the Java(tm) platform
0003: * ===========================================================
0004: *
0005: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0006: *
0007: * Project Info: http://www.jfree.org/jfreechart/index.html
0008: *
0009: * This library is free software; you can redistribute it and/or modify it
0010: * under the terms of the GNU Lesser General Public License as published by
0011: * the Free Software Foundation; either version 2.1 of the License, or
0012: * (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful, but
0015: * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
0016: * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
0017: * License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
0022: * USA.
0023: *
0024: * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
0025: * in the United States and other countries.]
0026: *
0027: * ---------------
0028: * NumberAxis.java
0029: * ---------------
0030: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0031: *
0032: * Original Author: David Gilbert (for Object Refinery Limited);
0033: * Contributor(s): Laurence Vanhelsuwe;
0034: *
0035: * $Id: NumberAxis.java,v 1.16.2.7 2007/03/22 12:13:27 mungady Exp $
0036: *
0037: * Changes (from 18-Sep-2001)
0038: * --------------------------
0039: * 18-Sep-2001 : Added standard header and fixed DOS encoding problem (DG);
0040: * 22-Sep-2001 : Changed setMinimumAxisValue() and setMaximumAxisValue() so
0041: * that they clear the autoRange flag (DG);
0042: * 27-Nov-2001 : Removed old, redundant code (DG);
0043: * 30-Nov-2001 : Added accessor methods for the standard tick units (DG);
0044: * 08-Jan-2002 : Added setAxisRange() method (since renamed setRange()) (DG);
0045: * 16-Jan-2002 : Added setTickUnit() method. Extended ValueAxis to support an
0046: * optional cross-hair (DG);
0047: * 08-Feb-2002 : Fixes bug to ensure the autorange is recalculated if the
0048: * setAutoRangeIncludesZero flag is changed (DG);
0049: * 25-Feb-2002 : Added a new flag autoRangeStickyZero to provide further
0050: * control over margins in the auto-range mechanism. Updated
0051: * constructors. Updated import statements. Moved the
0052: * createStandardTickUnits() method to the TickUnits class (DG);
0053: * 19-Apr-2002 : Updated Javadoc comments (DG);
0054: * 01-May-2002 : Updated for changes to TickUnit class, removed valueToString()
0055: * method (DG);
0056: * 25-Jul-2002 : Moved the lower and upper margin attributes, and the
0057: * auto-range minimum size, up one level to the ValueAxis
0058: * class (DG);
0059: * 05-Sep-2002 : Updated constructor to match changes in Axis class (DG);
0060: * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0061: * 04-Oct-2002 : Moved standardTickUnits from NumberAxis --> ValueAxis (DG);
0062: * 24-Oct-2002 : Added a number format override (DG);
0063: * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
0064: * 19-Nov-2002 : Removed grid settings (now controlled by the plot) (DG);
0065: * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double, and moved
0066: * crosshair settings to the plot classes (DG);
0067: * 20-Jan-2003 : Removed the monolithic constructor (DG);
0068: * 26-Mar-2003 : Implemented Serializable (DG);
0069: * 16-Jul-2003 : Reworked to allow for multiple secondary axes (DG);
0070: * 13-Aug-2003 : Implemented Cloneable (DG);
0071: * 07-Oct-2003 : Fixed bug (815028) in the auto range calculation (DG);
0072: * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
0073: * 07-Nov-2003 : Modified to use NumberTick class (DG);
0074: * 21-Jan-2004 : Renamed translateJava2DToValue --> java2DToValue, and
0075: * translateValueToJava2D --> valueToJava2D (DG);
0076: * 03-Mar-2004 : Added plotState to draw() method (DG);
0077: * 07-Apr-2004 : Changed string width calculation (DG);
0078: * 11-Jan-2005 : Removed deprecated methods in preparation for 1.0.0
0079: * release (DG);
0080: * 28-Mar-2005 : Renamed autoRangeIncludesZero() --> getAutoRangeIncludesZero()
0081: * and autoRangeStickyZero() --> getAutoRangeStickyZero() (DG);
0082: * 21-Apr-2005 : Removed redundant argument from selectAutoTickUnit() (DG);
0083: * 22-Apr-2005 : Renamed refreshHorizontalTicks --> refreshTicksHorizontal
0084: * (and likewise the vertical version) for consistency with
0085: * other axis classes (DG);
0086: * ------------- JFREECHART 1.0.x ---------------------------------------------
0087: * 10-Feb-2006 : Added some API doc comments in respect of bug 821046 (DG);
0088: * 20-Feb-2006 : Modified equals() method to check rangeType field (fixes bug
0089: * 1435461) (DG);
0090: * 04-Sep-2006 : Fix auto range calculation for the case where all data values
0091: * are constant and large (see bug report 1549218) (DG);
0092: * 11-Dec-2006 : Fix bug in auto-tick unit selection with tick format override,
0093: * see bug 1608371 (DG);
0094: * 22-Mar-2007 : Use new defaultAutoRange attribute (DG);
0095: *
0096: */
0097:
0098: package org.jfree.chart.axis;
0099:
0100: import java.awt.Font;
0101: import java.awt.FontMetrics;
0102: import java.awt.Graphics2D;
0103: import java.awt.font.FontRenderContext;
0104: import java.awt.font.LineMetrics;
0105: import java.awt.geom.Rectangle2D;
0106: import java.io.Serializable;
0107: import java.text.DecimalFormat;
0108: import java.text.NumberFormat;
0109: import java.util.List;
0110: import java.util.Locale;
0111:
0112: import org.jfree.chart.event.AxisChangeEvent;
0113: import org.jfree.chart.plot.Plot;
0114: import org.jfree.chart.plot.PlotRenderingInfo;
0115: import org.jfree.chart.plot.ValueAxisPlot;
0116: import org.jfree.data.Range;
0117: import org.jfree.data.RangeType;
0118: import org.jfree.ui.RectangleEdge;
0119: import org.jfree.ui.RectangleInsets;
0120: import org.jfree.ui.TextAnchor;
0121: import org.jfree.util.ObjectUtilities;
0122:
0123: /**
0124: * An axis for displaying numerical data.
0125: * <P>
0126: * If the axis is set up to automatically determine its range to fit the data,
0127: * you can ensure that the range includes zero (statisticians usually prefer
0128: * this) by setting the <code>autoRangeIncludesZero</code> flag to
0129: * <code>true</code>.
0130: * <P>
0131: * The <code>NumberAxis</code> class has a mechanism for automatically
0132: * selecting a tick unit that is appropriate for the current axis range. This
0133: * mechanism is an adaptation of code suggested by Laurence Vanhelsuwe.
0134: */
0135: public class NumberAxis extends ValueAxis implements Cloneable,
0136: Serializable {
0137:
0138: /** For serialization. */
0139: private static final long serialVersionUID = 2805933088476185789L;
0140:
0141: /** The default value for the autoRangeIncludesZero flag. */
0142: public static final boolean DEFAULT_AUTO_RANGE_INCLUDES_ZERO = true;
0143:
0144: /** The default value for the autoRangeStickyZero flag. */
0145: public static final boolean DEFAULT_AUTO_RANGE_STICKY_ZERO = true;
0146:
0147: /** The default tick unit. */
0148: public static final NumberTickUnit DEFAULT_TICK_UNIT = new NumberTickUnit(
0149: 1.0, new DecimalFormat("0"));
0150:
0151: /** The default setting for the vertical tick labels flag. */
0152: public static final boolean DEFAULT_VERTICAL_TICK_LABELS = false;
0153:
0154: /**
0155: * The range type (can be used to force the axis to display only positive
0156: * values or only negative values).
0157: */
0158: private RangeType rangeType;
0159:
0160: /**
0161: * A flag that affects the axis range when the range is determined
0162: * automatically. If the auto range does NOT include zero and this flag
0163: * is TRUE, then the range is changed to include zero.
0164: */
0165: private boolean autoRangeIncludesZero;
0166:
0167: /**
0168: * A flag that affects the size of the margins added to the axis range when
0169: * the range is determined automatically. If the value 0 falls within the
0170: * margin and this flag is TRUE, then the margin is truncated at zero.
0171: */
0172: private boolean autoRangeStickyZero;
0173:
0174: /** The tick unit for the axis. */
0175: private NumberTickUnit tickUnit;
0176:
0177: /** The override number format. */
0178: private NumberFormat numberFormatOverride;
0179:
0180: /** An optional band for marking regions on the axis. */
0181: private MarkerAxisBand markerBand;
0182:
0183: /**
0184: * Default constructor.
0185: */
0186: public NumberAxis() {
0187: this (null);
0188: }
0189:
0190: /**
0191: * Constructs a number axis, using default values where necessary.
0192: *
0193: * @param label the axis label (<code>null</code> permitted).
0194: */
0195: public NumberAxis(String label) {
0196: super (label, NumberAxis.createStandardTickUnits());
0197: this .rangeType = RangeType.FULL;
0198: this .autoRangeIncludesZero = DEFAULT_AUTO_RANGE_INCLUDES_ZERO;
0199: this .autoRangeStickyZero = DEFAULT_AUTO_RANGE_STICKY_ZERO;
0200: this .tickUnit = DEFAULT_TICK_UNIT;
0201: this .numberFormatOverride = null;
0202: this .markerBand = null;
0203: }
0204:
0205: /**
0206: * Returns the axis range type.
0207: *
0208: * @return The axis range type (never <code>null</code>).
0209: *
0210: * @see #setRangeType(RangeType)
0211: */
0212: public RangeType getRangeType() {
0213: return this .rangeType;
0214: }
0215:
0216: /**
0217: * Sets the axis range type.
0218: *
0219: * @param rangeType the range type (<code>null</code> not permitted).
0220: *
0221: * @see #getRangeType()
0222: */
0223: public void setRangeType(RangeType rangeType) {
0224: if (rangeType == null) {
0225: throw new IllegalArgumentException(
0226: "Null 'rangeType' argument.");
0227: }
0228: this .rangeType = rangeType;
0229: notifyListeners(new AxisChangeEvent(this ));
0230: }
0231:
0232: /**
0233: * Returns the flag that indicates whether or not the automatic axis range
0234: * (if indeed it is determined automatically) is forced to include zero.
0235: *
0236: * @return The flag.
0237: */
0238: public boolean getAutoRangeIncludesZero() {
0239: return this .autoRangeIncludesZero;
0240: }
0241:
0242: /**
0243: * Sets the flag that indicates whether or not the axis range, if
0244: * automatically calculated, is forced to include zero.
0245: * <p>
0246: * If the flag is changed to <code>true</code>, the axis range is
0247: * recalculated.
0248: * <p>
0249: * Any change to the flag will trigger an {@link AxisChangeEvent}.
0250: *
0251: * @param flag the new value of the flag.
0252: *
0253: * @see #getAutoRangeIncludesZero()
0254: */
0255: public void setAutoRangeIncludesZero(boolean flag) {
0256: if (this .autoRangeIncludesZero != flag) {
0257: this .autoRangeIncludesZero = flag;
0258: if (isAutoRange()) {
0259: autoAdjustRange();
0260: }
0261: notifyListeners(new AxisChangeEvent(this ));
0262: }
0263: }
0264:
0265: /**
0266: * Returns a flag that affects the auto-range when zero falls outside the
0267: * data range but inside the margins defined for the axis.
0268: *
0269: * @return The flag.
0270: *
0271: * @see #setAutoRangeStickyZero(boolean)
0272: */
0273: public boolean getAutoRangeStickyZero() {
0274: return this .autoRangeStickyZero;
0275: }
0276:
0277: /**
0278: * Sets a flag that affects the auto-range when zero falls outside the data
0279: * range but inside the margins defined for the axis.
0280: *
0281: * @param flag the new flag.
0282: *
0283: * @see #getAutoRangeStickyZero()
0284: */
0285: public void setAutoRangeStickyZero(boolean flag) {
0286: if (this .autoRangeStickyZero != flag) {
0287: this .autoRangeStickyZero = flag;
0288: if (isAutoRange()) {
0289: autoAdjustRange();
0290: }
0291: notifyListeners(new AxisChangeEvent(this ));
0292: }
0293: }
0294:
0295: /**
0296: * Returns the tick unit for the axis.
0297: * <p>
0298: * Note: if the <code>autoTickUnitSelection</code> flag is
0299: * <code>true</code> the tick unit may be changed while the axis is being
0300: * drawn, so in that case the return value from this method may be
0301: * irrelevant if the method is called before the axis has been drawn.
0302: *
0303: * @return The tick unit for the axis.
0304: *
0305: * @see #setTickUnit(NumberTickUnit)
0306: * @see ValueAxis#isAutoTickUnitSelection()
0307: */
0308: public NumberTickUnit getTickUnit() {
0309: return this .tickUnit;
0310: }
0311:
0312: /**
0313: * Sets the tick unit for the axis and sends an {@link AxisChangeEvent} to
0314: * all registered listeners. A side effect of calling this method is that
0315: * the "auto-select" feature for tick units is switched off (you can
0316: * restore it using the {@link ValueAxis#setAutoTickUnitSelection(boolean)}
0317: * method).
0318: *
0319: * @param unit the new tick unit (<code>null</code> not permitted).
0320: *
0321: * @see #getTickUnit()
0322: * @see #setTickUnit(NumberTickUnit, boolean, boolean)
0323: */
0324: public void setTickUnit(NumberTickUnit unit) {
0325: // defer argument checking...
0326: setTickUnit(unit, true, true);
0327: }
0328:
0329: /**
0330: * Sets the tick unit for the axis and, if requested, sends an
0331: * {@link AxisChangeEvent} to all registered listeners. In addition, an
0332: * option is provided to turn off the "auto-select" feature for tick units
0333: * (you can restore it using the
0334: * {@link ValueAxis#setAutoTickUnitSelection(boolean)} method).
0335: *
0336: * @param unit the new tick unit (<code>null</code> not permitted).
0337: * @param notify notify listeners?
0338: * @param turnOffAutoSelect turn off the auto-tick selection?
0339: */
0340: public void setTickUnit(NumberTickUnit unit, boolean notify,
0341: boolean turnOffAutoSelect) {
0342:
0343: if (unit == null) {
0344: throw new IllegalArgumentException("Null 'unit' argument.");
0345: }
0346: this .tickUnit = unit;
0347: if (turnOffAutoSelect) {
0348: setAutoTickUnitSelection(false, false);
0349: }
0350: if (notify) {
0351: notifyListeners(new AxisChangeEvent(this ));
0352: }
0353:
0354: }
0355:
0356: /**
0357: * Returns the number format override. If this is non-null, then it will
0358: * be used to format the numbers on the axis.
0359: *
0360: * @return The number formatter (possibly <code>null</code>).
0361: *
0362: * @see #setNumberFormatOverride(NumberFormat)
0363: */
0364: public NumberFormat getNumberFormatOverride() {
0365: return this .numberFormatOverride;
0366: }
0367:
0368: /**
0369: * Sets the number format override. If this is non-null, then it will be
0370: * used to format the numbers on the axis.
0371: *
0372: * @param formatter the number formatter (<code>null</code> permitted).
0373: *
0374: * @see #getNumberFormatOverride()
0375: */
0376: public void setNumberFormatOverride(NumberFormat formatter) {
0377: this .numberFormatOverride = formatter;
0378: notifyListeners(new AxisChangeEvent(this ));
0379: }
0380:
0381: /**
0382: * Returns the (optional) marker band for the axis.
0383: *
0384: * @return The marker band (possibly <code>null</code>).
0385: *
0386: * @see #setMarkerBand(MarkerAxisBand)
0387: */
0388: public MarkerAxisBand getMarkerBand() {
0389: return this .markerBand;
0390: }
0391:
0392: /**
0393: * Sets the marker band for the axis.
0394: * <P>
0395: * The marker band is optional, leave it set to <code>null</code> if you
0396: * don't require it.
0397: *
0398: * @param band the new band (<code>null<code> permitted).
0399: *
0400: * @see #getMarkerBand()
0401: */
0402: public void setMarkerBand(MarkerAxisBand band) {
0403: this .markerBand = band;
0404: notifyListeners(new AxisChangeEvent(this ));
0405: }
0406:
0407: /**
0408: * Configures the axis to work with the specified plot. If the axis has
0409: * auto-scaling, then sets the maximum and minimum values.
0410: */
0411: public void configure() {
0412: if (isAutoRange()) {
0413: autoAdjustRange();
0414: }
0415: }
0416:
0417: /**
0418: * Rescales the axis to ensure that all data is visible.
0419: */
0420: protected void autoAdjustRange() {
0421:
0422: Plot plot = getPlot();
0423: if (plot == null) {
0424: return; // no plot, no data
0425: }
0426:
0427: if (plot instanceof ValueAxisPlot) {
0428: ValueAxisPlot vap = (ValueAxisPlot) plot;
0429:
0430: Range r = vap.getDataRange(this );
0431: if (r == null) {
0432: r = getDefaultAutoRange();
0433: }
0434:
0435: double upper = r.getUpperBound();
0436: double lower = r.getLowerBound();
0437: if (this .rangeType == RangeType.POSITIVE) {
0438: lower = Math.max(0.0, lower);
0439: upper = Math.max(0.0, upper);
0440: } else if (this .rangeType == RangeType.NEGATIVE) {
0441: lower = Math.min(0.0, lower);
0442: upper = Math.min(0.0, upper);
0443: }
0444:
0445: if (getAutoRangeIncludesZero()) {
0446: lower = Math.min(lower, 0.0);
0447: upper = Math.max(upper, 0.0);
0448: }
0449: double range = upper - lower;
0450:
0451: // if fixed auto range, then derive lower bound...
0452: double fixedAutoRange = getFixedAutoRange();
0453: if (fixedAutoRange > 0.0) {
0454: lower = upper - fixedAutoRange;
0455: } else {
0456: // ensure the autorange is at least <minRange> in size...
0457: double minRange = getAutoRangeMinimumSize();
0458: if (range < minRange) {
0459: double expand = (minRange - range) / 2;
0460: upper = upper + expand;
0461: lower = lower - expand;
0462: if (lower == upper) { // see bug report 1549218
0463: double adjust = Math.abs(lower) / 10.0;
0464: lower = lower - adjust;
0465: upper = upper + adjust;
0466: }
0467: if (this .rangeType == RangeType.POSITIVE) {
0468: if (lower < 0.0) {
0469: upper = upper - lower;
0470: lower = 0.0;
0471: }
0472: } else if (this .rangeType == RangeType.NEGATIVE) {
0473: if (upper > 0.0) {
0474: lower = lower - upper;
0475: upper = 0.0;
0476: }
0477: }
0478: }
0479:
0480: if (getAutoRangeStickyZero()) {
0481: if (upper <= 0.0) {
0482: upper = Math.min(0.0, upper + getUpperMargin()
0483: * range);
0484: } else {
0485: upper = upper + getUpperMargin() * range;
0486: }
0487: if (lower >= 0.0) {
0488: lower = Math.max(0.0, lower - getLowerMargin()
0489: * range);
0490: } else {
0491: lower = lower - getLowerMargin() * range;
0492: }
0493: } else {
0494: upper = upper + getUpperMargin() * range;
0495: lower = lower - getLowerMargin() * range;
0496: }
0497: }
0498:
0499: setRange(new Range(lower, upper), false, false);
0500: }
0501:
0502: }
0503:
0504: /**
0505: * Converts a data value to a coordinate in Java2D space, assuming that the
0506: * axis runs along one edge of the specified dataArea.
0507: * <p>
0508: * Note that it is possible for the coordinate to fall outside the plotArea.
0509: *
0510: * @param value the data value.
0511: * @param area the area for plotting the data.
0512: * @param edge the axis location.
0513: *
0514: * @return The Java2D coordinate.
0515: *
0516: * @see #java2DToValue(double, Rectangle2D, RectangleEdge)
0517: */
0518: public double valueToJava2D(double value, Rectangle2D area,
0519: RectangleEdge edge) {
0520:
0521: Range range = getRange();
0522: double axisMin = range.getLowerBound();
0523: double axisMax = range.getUpperBound();
0524:
0525: double min = 0.0;
0526: double max = 0.0;
0527: if (RectangleEdge.isTopOrBottom(edge)) {
0528: min = area.getX();
0529: max = area.getMaxX();
0530: } else if (RectangleEdge.isLeftOrRight(edge)) {
0531: max = area.getMinY();
0532: min = area.getMaxY();
0533: }
0534: if (isInverted()) {
0535: return max - ((value - axisMin) / (axisMax - axisMin))
0536: * (max - min);
0537: } else {
0538: return min + ((value - axisMin) / (axisMax - axisMin))
0539: * (max - min);
0540: }
0541:
0542: }
0543:
0544: /**
0545: * Converts a coordinate in Java2D space to the corresponding data value,
0546: * assuming that the axis runs along one edge of the specified dataArea.
0547: *
0548: * @param java2DValue the coordinate in Java2D space.
0549: * @param area the area in which the data is plotted.
0550: * @param edge the location.
0551: *
0552: * @return The data value.
0553: *
0554: * @see #valueToJava2D(double, Rectangle2D, RectangleEdge)
0555: */
0556: public double java2DToValue(double java2DValue, Rectangle2D area,
0557: RectangleEdge edge) {
0558:
0559: Range range = getRange();
0560: double axisMin = range.getLowerBound();
0561: double axisMax = range.getUpperBound();
0562:
0563: double min = 0.0;
0564: double max = 0.0;
0565: if (RectangleEdge.isTopOrBottom(edge)) {
0566: min = area.getX();
0567: max = area.getMaxX();
0568: } else if (RectangleEdge.isLeftOrRight(edge)) {
0569: min = area.getMaxY();
0570: max = area.getY();
0571: }
0572: if (isInverted()) {
0573: return axisMax - (java2DValue - min) / (max - min)
0574: * (axisMax - axisMin);
0575: } else {
0576: return axisMin + (java2DValue - min) / (max - min)
0577: * (axisMax - axisMin);
0578: }
0579:
0580: }
0581:
0582: /**
0583: * Calculates the value of the lowest visible tick on the axis.
0584: *
0585: * @return The value of the lowest visible tick on the axis.
0586: *
0587: * @see #calculateHighestVisibleTickValue()
0588: */
0589: protected double calculateLowestVisibleTickValue() {
0590:
0591: double unit = getTickUnit().getSize();
0592: double index = Math.ceil(getRange().getLowerBound() / unit);
0593: return index * unit;
0594:
0595: }
0596:
0597: /**
0598: * Calculates the value of the highest visible tick on the axis.
0599: *
0600: * @return The value of the highest visible tick on the axis.
0601: *
0602: * @see #calculateLowestVisibleTickValue()
0603: */
0604: protected double calculateHighestVisibleTickValue() {
0605:
0606: double unit = getTickUnit().getSize();
0607: double index = Math.floor(getRange().getUpperBound() / unit);
0608: return index * unit;
0609:
0610: }
0611:
0612: /**
0613: * Calculates the number of visible ticks.
0614: *
0615: * @return The number of visible ticks on the axis.
0616: */
0617: protected int calculateVisibleTickCount() {
0618:
0619: double unit = getTickUnit().getSize();
0620: Range range = getRange();
0621: return (int) (Math.floor(range.getUpperBound() / unit)
0622: - Math.ceil(range.getLowerBound() / unit) + 1);
0623:
0624: }
0625:
0626: /**
0627: * Draws the axis on a Java 2D graphics device (such as the screen or a
0628: * printer).
0629: *
0630: * @param g2 the graphics device (<code>null</code> not permitted).
0631: * @param cursor the cursor location.
0632: * @param plotArea the area within which the axes and data should be drawn
0633: * (<code>null</code> not permitted).
0634: * @param dataArea the area within which the data should be drawn
0635: * (<code>null</code> not permitted).
0636: * @param edge the location of the axis (<code>null</code> not permitted).
0637: * @param plotState collects information about the plot
0638: * (<code>null</code> permitted).
0639: *
0640: * @return The axis state (never <code>null</code>).
0641: */
0642: public AxisState draw(Graphics2D g2, double cursor,
0643: Rectangle2D plotArea, Rectangle2D dataArea,
0644: RectangleEdge edge, PlotRenderingInfo plotState) {
0645:
0646: AxisState state = null;
0647: // if the axis is not visible, don't draw it...
0648: if (!isVisible()) {
0649: state = new AxisState(cursor);
0650: // even though the axis is not visible, we need ticks for the
0651: // gridlines...
0652: List ticks = refreshTicks(g2, state, dataArea, edge);
0653: state.setTicks(ticks);
0654: return state;
0655: }
0656:
0657: // draw the tick marks and labels...
0658: state = drawTickMarksAndLabels(g2, cursor, plotArea, dataArea,
0659: edge);
0660:
0661: // // draw the marker band (if there is one)...
0662: // if (getMarkerBand() != null) {
0663: // if (edge == RectangleEdge.BOTTOM) {
0664: // cursor = cursor - getMarkerBand().getHeight(g2);
0665: // }
0666: // getMarkerBand().draw(g2, plotArea, dataArea, 0, cursor);
0667: // }
0668:
0669: // draw the axis label...
0670: state = drawLabel(getLabel(), g2, plotArea, dataArea, edge,
0671: state);
0672:
0673: return state;
0674:
0675: }
0676:
0677: /**
0678: * Creates the standard tick units.
0679: * <P>
0680: * If you don't like these defaults, create your own instance of TickUnits
0681: * and then pass it to the setStandardTickUnits() method in the
0682: * NumberAxis class.
0683: *
0684: * @return The standard tick units.
0685: *
0686: * @see #setStandardTickUnits(TickUnitSource)
0687: * @see #createIntegerTickUnits()
0688: */
0689: public static TickUnitSource createStandardTickUnits() {
0690:
0691: TickUnits units = new TickUnits();
0692: DecimalFormat df0 = new DecimalFormat("0.00000000");
0693: DecimalFormat df1 = new DecimalFormat("0.0000000");
0694: DecimalFormat df2 = new DecimalFormat("0.000000");
0695: DecimalFormat df3 = new DecimalFormat("0.00000");
0696: DecimalFormat df4 = new DecimalFormat("0.0000");
0697: DecimalFormat df5 = new DecimalFormat("0.000");
0698: DecimalFormat df6 = new DecimalFormat("0.00");
0699: DecimalFormat df7 = new DecimalFormat("0.0");
0700: DecimalFormat df8 = new DecimalFormat("#,##0");
0701: DecimalFormat df9 = new DecimalFormat("#,###,##0");
0702: DecimalFormat df10 = new DecimalFormat("#,###,###,##0");
0703:
0704: // we can add the units in any order, the TickUnits collection will
0705: // sort them...
0706: units.add(new NumberTickUnit(0.0000001, df1));
0707: units.add(new NumberTickUnit(0.000001, df2));
0708: units.add(new NumberTickUnit(0.00001, df3));
0709: units.add(new NumberTickUnit(0.0001, df4));
0710: units.add(new NumberTickUnit(0.001, df5));
0711: units.add(new NumberTickUnit(0.01, df6));
0712: units.add(new NumberTickUnit(0.1, df7));
0713: units.add(new NumberTickUnit(1, df8));
0714: units.add(new NumberTickUnit(10, df8));
0715: units.add(new NumberTickUnit(100, df8));
0716: units.add(new NumberTickUnit(1000, df8));
0717: units.add(new NumberTickUnit(10000, df8));
0718: units.add(new NumberTickUnit(100000, df8));
0719: units.add(new NumberTickUnit(1000000, df9));
0720: units.add(new NumberTickUnit(10000000, df9));
0721: units.add(new NumberTickUnit(100000000, df9));
0722: units.add(new NumberTickUnit(1000000000, df10));
0723: units.add(new NumberTickUnit(10000000000.0, df10));
0724: units.add(new NumberTickUnit(100000000000.0, df10));
0725:
0726: units.add(new NumberTickUnit(0.00000025, df0));
0727: units.add(new NumberTickUnit(0.0000025, df1));
0728: units.add(new NumberTickUnit(0.000025, df2));
0729: units.add(new NumberTickUnit(0.00025, df3));
0730: units.add(new NumberTickUnit(0.0025, df4));
0731: units.add(new NumberTickUnit(0.025, df5));
0732: units.add(new NumberTickUnit(0.25, df6));
0733: units.add(new NumberTickUnit(2.5, df7));
0734: units.add(new NumberTickUnit(25, df8));
0735: units.add(new NumberTickUnit(250, df8));
0736: units.add(new NumberTickUnit(2500, df8));
0737: units.add(new NumberTickUnit(25000, df8));
0738: units.add(new NumberTickUnit(250000, df8));
0739: units.add(new NumberTickUnit(2500000, df9));
0740: units.add(new NumberTickUnit(25000000, df9));
0741: units.add(new NumberTickUnit(250000000, df9));
0742: units.add(new NumberTickUnit(2500000000.0, df10));
0743: units.add(new NumberTickUnit(25000000000.0, df10));
0744: units.add(new NumberTickUnit(250000000000.0, df10));
0745:
0746: units.add(new NumberTickUnit(0.0000005, df1));
0747: units.add(new NumberTickUnit(0.000005, df2));
0748: units.add(new NumberTickUnit(0.00005, df3));
0749: units.add(new NumberTickUnit(0.0005, df4));
0750: units.add(new NumberTickUnit(0.005, df5));
0751: units.add(new NumberTickUnit(0.05, df6));
0752: units.add(new NumberTickUnit(0.5, df7));
0753: units.add(new NumberTickUnit(5L, df8));
0754: units.add(new NumberTickUnit(50L, df8));
0755: units.add(new NumberTickUnit(500L, df8));
0756: units.add(new NumberTickUnit(5000L, df8));
0757: units.add(new NumberTickUnit(50000L, df8));
0758: units.add(new NumberTickUnit(500000L, df8));
0759: units.add(new NumberTickUnit(5000000L, df9));
0760: units.add(new NumberTickUnit(50000000L, df9));
0761: units.add(new NumberTickUnit(500000000L, df9));
0762: units.add(new NumberTickUnit(5000000000L, df10));
0763: units.add(new NumberTickUnit(50000000000L, df10));
0764: units.add(new NumberTickUnit(500000000000L, df10));
0765:
0766: return units;
0767:
0768: }
0769:
0770: /**
0771: * Returns a collection of tick units for integer values.
0772: *
0773: * @return A collection of tick units for integer values.
0774: *
0775: * @see #setStandardTickUnits(TickUnitSource)
0776: * @see #createStandardTickUnits()
0777: */
0778: public static TickUnitSource createIntegerTickUnits() {
0779:
0780: TickUnits units = new TickUnits();
0781: DecimalFormat df0 = new DecimalFormat("0");
0782: DecimalFormat df1 = new DecimalFormat("#,##0");
0783: units.add(new NumberTickUnit(1, df0));
0784: units.add(new NumberTickUnit(2, df0));
0785: units.add(new NumberTickUnit(5, df0));
0786: units.add(new NumberTickUnit(10, df0));
0787: units.add(new NumberTickUnit(20, df0));
0788: units.add(new NumberTickUnit(50, df0));
0789: units.add(new NumberTickUnit(100, df0));
0790: units.add(new NumberTickUnit(200, df0));
0791: units.add(new NumberTickUnit(500, df0));
0792: units.add(new NumberTickUnit(1000, df1));
0793: units.add(new NumberTickUnit(2000, df1));
0794: units.add(new NumberTickUnit(5000, df1));
0795: units.add(new NumberTickUnit(10000, df1));
0796: units.add(new NumberTickUnit(20000, df1));
0797: units.add(new NumberTickUnit(50000, df1));
0798: units.add(new NumberTickUnit(100000, df1));
0799: units.add(new NumberTickUnit(200000, df1));
0800: units.add(new NumberTickUnit(500000, df1));
0801: units.add(new NumberTickUnit(1000000, df1));
0802: units.add(new NumberTickUnit(2000000, df1));
0803: units.add(new NumberTickUnit(5000000, df1));
0804: units.add(new NumberTickUnit(10000000, df1));
0805: units.add(new NumberTickUnit(20000000, df1));
0806: units.add(new NumberTickUnit(50000000, df1));
0807: units.add(new NumberTickUnit(100000000, df1));
0808: units.add(new NumberTickUnit(200000000, df1));
0809: units.add(new NumberTickUnit(500000000, df1));
0810: units.add(new NumberTickUnit(1000000000, df1));
0811: units.add(new NumberTickUnit(2000000000, df1));
0812: units.add(new NumberTickUnit(5000000000.0, df1));
0813: units.add(new NumberTickUnit(10000000000.0, df1));
0814:
0815: return units;
0816:
0817: }
0818:
0819: /**
0820: * Creates a collection of standard tick units. The supplied locale is
0821: * used to create the number formatter (a localised instance of
0822: * <code>NumberFormat</code>).
0823: * <P>
0824: * If you don't like these defaults, create your own instance of
0825: * {@link TickUnits} and then pass it to the
0826: * <code>setStandardTickUnits()</code> method.
0827: *
0828: * @param locale the locale.
0829: *
0830: * @return A tick unit collection.
0831: *
0832: * @see #setStandardTickUnits(TickUnitSource)
0833: */
0834: public static TickUnitSource createStandardTickUnits(Locale locale) {
0835:
0836: TickUnits units = new TickUnits();
0837:
0838: NumberFormat numberFormat = NumberFormat
0839: .getNumberInstance(locale);
0840:
0841: // we can add the units in any order, the TickUnits collection will
0842: // sort them...
0843: units.add(new NumberTickUnit(0.0000001, numberFormat));
0844: units.add(new NumberTickUnit(0.000001, numberFormat));
0845: units.add(new NumberTickUnit(0.00001, numberFormat));
0846: units.add(new NumberTickUnit(0.0001, numberFormat));
0847: units.add(new NumberTickUnit(0.001, numberFormat));
0848: units.add(new NumberTickUnit(0.01, numberFormat));
0849: units.add(new NumberTickUnit(0.1, numberFormat));
0850: units.add(new NumberTickUnit(1, numberFormat));
0851: units.add(new NumberTickUnit(10, numberFormat));
0852: units.add(new NumberTickUnit(100, numberFormat));
0853: units.add(new NumberTickUnit(1000, numberFormat));
0854: units.add(new NumberTickUnit(10000, numberFormat));
0855: units.add(new NumberTickUnit(100000, numberFormat));
0856: units.add(new NumberTickUnit(1000000, numberFormat));
0857: units.add(new NumberTickUnit(10000000, numberFormat));
0858: units.add(new NumberTickUnit(100000000, numberFormat));
0859: units.add(new NumberTickUnit(1000000000, numberFormat));
0860: units.add(new NumberTickUnit(10000000000.0, numberFormat));
0861:
0862: units.add(new NumberTickUnit(0.00000025, numberFormat));
0863: units.add(new NumberTickUnit(0.0000025, numberFormat));
0864: units.add(new NumberTickUnit(0.000025, numberFormat));
0865: units.add(new NumberTickUnit(0.00025, numberFormat));
0866: units.add(new NumberTickUnit(0.0025, numberFormat));
0867: units.add(new NumberTickUnit(0.025, numberFormat));
0868: units.add(new NumberTickUnit(0.25, numberFormat));
0869: units.add(new NumberTickUnit(2.5, numberFormat));
0870: units.add(new NumberTickUnit(25, numberFormat));
0871: units.add(new NumberTickUnit(250, numberFormat));
0872: units.add(new NumberTickUnit(2500, numberFormat));
0873: units.add(new NumberTickUnit(25000, numberFormat));
0874: units.add(new NumberTickUnit(250000, numberFormat));
0875: units.add(new NumberTickUnit(2500000, numberFormat));
0876: units.add(new NumberTickUnit(25000000, numberFormat));
0877: units.add(new NumberTickUnit(250000000, numberFormat));
0878: units.add(new NumberTickUnit(2500000000.0, numberFormat));
0879: units.add(new NumberTickUnit(25000000000.0, numberFormat));
0880:
0881: units.add(new NumberTickUnit(0.0000005, numberFormat));
0882: units.add(new NumberTickUnit(0.000005, numberFormat));
0883: units.add(new NumberTickUnit(0.00005, numberFormat));
0884: units.add(new NumberTickUnit(0.0005, numberFormat));
0885: units.add(new NumberTickUnit(0.005, numberFormat));
0886: units.add(new NumberTickUnit(0.05, numberFormat));
0887: units.add(new NumberTickUnit(0.5, numberFormat));
0888: units.add(new NumberTickUnit(5L, numberFormat));
0889: units.add(new NumberTickUnit(50L, numberFormat));
0890: units.add(new NumberTickUnit(500L, numberFormat));
0891: units.add(new NumberTickUnit(5000L, numberFormat));
0892: units.add(new NumberTickUnit(50000L, numberFormat));
0893: units.add(new NumberTickUnit(500000L, numberFormat));
0894: units.add(new NumberTickUnit(5000000L, numberFormat));
0895: units.add(new NumberTickUnit(50000000L, numberFormat));
0896: units.add(new NumberTickUnit(500000000L, numberFormat));
0897: units.add(new NumberTickUnit(5000000000L, numberFormat));
0898: units.add(new NumberTickUnit(50000000000L, numberFormat));
0899:
0900: return units;
0901:
0902: }
0903:
0904: /**
0905: * Returns a collection of tick units for integer values.
0906: * Uses a given Locale to create the DecimalFormats.
0907: *
0908: * @param locale the locale to use to represent Numbers.
0909: *
0910: * @return A collection of tick units for integer values.
0911: *
0912: * @see #setStandardTickUnits(TickUnitSource)
0913: */
0914: public static TickUnitSource createIntegerTickUnits(Locale locale) {
0915:
0916: TickUnits units = new TickUnits();
0917:
0918: NumberFormat numberFormat = NumberFormat
0919: .getNumberInstance(locale);
0920:
0921: units.add(new NumberTickUnit(1, numberFormat));
0922: units.add(new NumberTickUnit(2, numberFormat));
0923: units.add(new NumberTickUnit(5, numberFormat));
0924: units.add(new NumberTickUnit(10, numberFormat));
0925: units.add(new NumberTickUnit(20, numberFormat));
0926: units.add(new NumberTickUnit(50, numberFormat));
0927: units.add(new NumberTickUnit(100, numberFormat));
0928: units.add(new NumberTickUnit(200, numberFormat));
0929: units.add(new NumberTickUnit(500, numberFormat));
0930: units.add(new NumberTickUnit(1000, numberFormat));
0931: units.add(new NumberTickUnit(2000, numberFormat));
0932: units.add(new NumberTickUnit(5000, numberFormat));
0933: units.add(new NumberTickUnit(10000, numberFormat));
0934: units.add(new NumberTickUnit(20000, numberFormat));
0935: units.add(new NumberTickUnit(50000, numberFormat));
0936: units.add(new NumberTickUnit(100000, numberFormat));
0937: units.add(new NumberTickUnit(200000, numberFormat));
0938: units.add(new NumberTickUnit(500000, numberFormat));
0939: units.add(new NumberTickUnit(1000000, numberFormat));
0940: units.add(new NumberTickUnit(2000000, numberFormat));
0941: units.add(new NumberTickUnit(5000000, numberFormat));
0942: units.add(new NumberTickUnit(10000000, numberFormat));
0943: units.add(new NumberTickUnit(20000000, numberFormat));
0944: units.add(new NumberTickUnit(50000000, numberFormat));
0945: units.add(new NumberTickUnit(100000000, numberFormat));
0946: units.add(new NumberTickUnit(200000000, numberFormat));
0947: units.add(new NumberTickUnit(500000000, numberFormat));
0948: units.add(new NumberTickUnit(1000000000, numberFormat));
0949: units.add(new NumberTickUnit(2000000000, numberFormat));
0950: units.add(new NumberTickUnit(5000000000.0, numberFormat));
0951: units.add(new NumberTickUnit(10000000000.0, numberFormat));
0952:
0953: return units;
0954:
0955: }
0956:
0957: /**
0958: * Estimates the maximum tick label height.
0959: *
0960: * @param g2 the graphics device.
0961: *
0962: * @return The maximum height.
0963: */
0964: protected double estimateMaximumTickLabelHeight(Graphics2D g2) {
0965:
0966: RectangleInsets tickLabelInsets = getTickLabelInsets();
0967: double result = tickLabelInsets.getTop()
0968: + tickLabelInsets.getBottom();
0969:
0970: Font tickLabelFont = getTickLabelFont();
0971: FontRenderContext frc = g2.getFontRenderContext();
0972: result += tickLabelFont.getLineMetrics("123", frc).getHeight();
0973: return result;
0974:
0975: }
0976:
0977: /**
0978: * Estimates the maximum width of the tick labels, assuming the specified
0979: * tick unit is used.
0980: * <P>
0981: * Rather than computing the string bounds of every tick on the axis, we
0982: * just look at two values: the lower bound and the upper bound for the
0983: * axis. These two values will usually be representative.
0984: *
0985: * @param g2 the graphics device.
0986: * @param unit the tick unit to use for calculation.
0987: *
0988: * @return The estimated maximum width of the tick labels.
0989: */
0990: protected double estimateMaximumTickLabelWidth(Graphics2D g2,
0991: TickUnit unit) {
0992:
0993: RectangleInsets tickLabelInsets = getTickLabelInsets();
0994: double result = tickLabelInsets.getLeft()
0995: + tickLabelInsets.getRight();
0996:
0997: if (isVerticalTickLabels()) {
0998: // all tick labels have the same width (equal to the height of the
0999: // font)...
1000: FontRenderContext frc = g2.getFontRenderContext();
1001: LineMetrics lm = getTickLabelFont()
1002: .getLineMetrics("0", frc);
1003: result += lm.getHeight();
1004: } else {
1005: // look at lower and upper bounds...
1006: FontMetrics fm = g2.getFontMetrics(getTickLabelFont());
1007: Range range = getRange();
1008: double lower = range.getLowerBound();
1009: double upper = range.getUpperBound();
1010: String lowerStr = "";
1011: String upperStr = "";
1012: NumberFormat formatter = getNumberFormatOverride();
1013: if (formatter != null) {
1014: lowerStr = formatter.format(lower);
1015: upperStr = formatter.format(upper);
1016: } else {
1017: lowerStr = unit.valueToString(lower);
1018: upperStr = unit.valueToString(upper);
1019: }
1020: double w1 = fm.stringWidth(lowerStr);
1021: double w2 = fm.stringWidth(upperStr);
1022: result += Math.max(w1, w2);
1023: }
1024:
1025: return result;
1026:
1027: }
1028:
1029: /**
1030: * Selects an appropriate tick value for the axis. The strategy is to
1031: * display as many ticks as possible (selected from an array of 'standard'
1032: * tick units) without the labels overlapping.
1033: *
1034: * @param g2 the graphics device.
1035: * @param dataArea the area defined by the axes.
1036: * @param edge the axis location.
1037: */
1038: protected void selectAutoTickUnit(Graphics2D g2,
1039: Rectangle2D dataArea, RectangleEdge edge) {
1040:
1041: if (RectangleEdge.isTopOrBottom(edge)) {
1042: selectHorizontalAutoTickUnit(g2, dataArea, edge);
1043: } else if (RectangleEdge.isLeftOrRight(edge)) {
1044: selectVerticalAutoTickUnit(g2, dataArea, edge);
1045: }
1046:
1047: }
1048:
1049: /**
1050: * Selects an appropriate tick value for the axis. The strategy is to
1051: * display as many ticks as possible (selected from an array of 'standard'
1052: * tick units) without the labels overlapping.
1053: *
1054: * @param g2 the graphics device.
1055: * @param dataArea the area defined by the axes.
1056: * @param edge the axis location.
1057: */
1058: protected void selectHorizontalAutoTickUnit(Graphics2D g2,
1059: Rectangle2D dataArea, RectangleEdge edge) {
1060:
1061: double tickLabelWidth = estimateMaximumTickLabelWidth(g2,
1062: getTickUnit());
1063:
1064: // start with the current tick unit...
1065: TickUnitSource tickUnits = getStandardTickUnits();
1066: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1067: double unit1Width = lengthToJava2D(unit1.getSize(), dataArea,
1068: edge);
1069:
1070: // then extrapolate...
1071: double guess = (tickLabelWidth / unit1Width) * unit1.getSize();
1072:
1073: NumberTickUnit unit2 = (NumberTickUnit) tickUnits
1074: .getCeilingTickUnit(guess);
1075: double unit2Width = lengthToJava2D(unit2.getSize(), dataArea,
1076: edge);
1077:
1078: tickLabelWidth = estimateMaximumTickLabelWidth(g2, unit2);
1079: if (tickLabelWidth > unit2Width) {
1080: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
1081: }
1082:
1083: setTickUnit(unit2, false, false);
1084:
1085: }
1086:
1087: /**
1088: * Selects an appropriate tick value for the axis. The strategy is to
1089: * display as many ticks as possible (selected from an array of 'standard'
1090: * tick units) without the labels overlapping.
1091: *
1092: * @param g2 the graphics device.
1093: * @param dataArea the area in which the plot should be drawn.
1094: * @param edge the axis location.
1095: */
1096: protected void selectVerticalAutoTickUnit(Graphics2D g2,
1097: Rectangle2D dataArea, RectangleEdge edge) {
1098:
1099: double tickLabelHeight = estimateMaximumTickLabelHeight(g2);
1100:
1101: // start with the current tick unit...
1102: TickUnitSource tickUnits = getStandardTickUnits();
1103: TickUnit unit1 = tickUnits.getCeilingTickUnit(getTickUnit());
1104: double unitHeight = lengthToJava2D(unit1.getSize(), dataArea,
1105: edge);
1106:
1107: // then extrapolate...
1108: double guess = (tickLabelHeight / unitHeight) * unit1.getSize();
1109:
1110: NumberTickUnit unit2 = (NumberTickUnit) tickUnits
1111: .getCeilingTickUnit(guess);
1112: double unit2Height = lengthToJava2D(unit2.getSize(), dataArea,
1113: edge);
1114:
1115: tickLabelHeight = estimateMaximumTickLabelHeight(g2);
1116: if (tickLabelHeight > unit2Height) {
1117: unit2 = (NumberTickUnit) tickUnits.getLargerTickUnit(unit2);
1118: }
1119:
1120: setTickUnit(unit2, false, false);
1121:
1122: }
1123:
1124: /**
1125: * Calculates the positions of the tick labels for the axis, storing the
1126: * results in the tick label list (ready for drawing).
1127: *
1128: * @param g2 the graphics device.
1129: * @param state the axis state.
1130: * @param dataArea the area in which the plot should be drawn.
1131: * @param edge the location of the axis.
1132: *
1133: * @return A list of ticks.
1134: *
1135: */
1136: public List refreshTicks(Graphics2D g2, AxisState state,
1137: Rectangle2D dataArea, RectangleEdge edge) {
1138:
1139: List result = new java.util.ArrayList();
1140: if (RectangleEdge.isTopOrBottom(edge)) {
1141: result = refreshTicksHorizontal(g2, dataArea, edge);
1142: } else if (RectangleEdge.isLeftOrRight(edge)) {
1143: result = refreshTicksVertical(g2, dataArea, edge);
1144: }
1145: return result;
1146:
1147: }
1148:
1149: /**
1150: * Calculates the positions of the tick labels for the axis, storing the
1151: * results in the tick label list (ready for drawing).
1152: *
1153: * @param g2 the graphics device.
1154: * @param dataArea the area in which the data should be drawn.
1155: * @param edge the location of the axis.
1156: *
1157: * @return A list of ticks.
1158: */
1159: protected List refreshTicksHorizontal(Graphics2D g2,
1160: Rectangle2D dataArea, RectangleEdge edge) {
1161:
1162: List result = new java.util.ArrayList();
1163:
1164: Font tickLabelFont = getTickLabelFont();
1165: g2.setFont(tickLabelFont);
1166:
1167: if (isAutoTickUnitSelection()) {
1168: selectAutoTickUnit(g2, dataArea, edge);
1169: }
1170:
1171: double size = getTickUnit().getSize();
1172: int count = calculateVisibleTickCount();
1173: double lowestTickValue = calculateLowestVisibleTickValue();
1174:
1175: if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
1176: for (int i = 0; i < count; i++) {
1177: double currentTickValue = lowestTickValue + (i * size);
1178: String tickLabel;
1179: NumberFormat formatter = getNumberFormatOverride();
1180: if (formatter != null) {
1181: tickLabel = formatter.format(currentTickValue);
1182: } else {
1183: tickLabel = getTickUnit().valueToString(
1184: currentTickValue);
1185: }
1186: TextAnchor anchor = null;
1187: TextAnchor rotationAnchor = null;
1188: double angle = 0.0;
1189: if (isVerticalTickLabels()) {
1190: anchor = TextAnchor.CENTER_RIGHT;
1191: rotationAnchor = TextAnchor.CENTER_RIGHT;
1192: if (edge == RectangleEdge.TOP) {
1193: angle = Math.PI / 2.0;
1194: } else {
1195: angle = -Math.PI / 2.0;
1196: }
1197: } else {
1198: if (edge == RectangleEdge.TOP) {
1199: anchor = TextAnchor.BOTTOM_CENTER;
1200: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1201: } else {
1202: anchor = TextAnchor.TOP_CENTER;
1203: rotationAnchor = TextAnchor.TOP_CENTER;
1204: }
1205: }
1206:
1207: Tick tick = new NumberTick(
1208: new Double(currentTickValue), tickLabel,
1209: anchor, rotationAnchor, angle);
1210: result.add(tick);
1211: }
1212: }
1213: return result;
1214:
1215: }
1216:
1217: /**
1218: * Calculates the positions of the tick labels for the axis, storing the
1219: * results in the tick label list (ready for drawing).
1220: *
1221: * @param g2 the graphics device.
1222: * @param dataArea the area in which the plot should be drawn.
1223: * @param edge the location of the axis.
1224: *
1225: * @return A list of ticks.
1226: *
1227: */
1228: protected List refreshTicksVertical(Graphics2D g2,
1229: Rectangle2D dataArea, RectangleEdge edge) {
1230:
1231: List result = new java.util.ArrayList();
1232: result.clear();
1233:
1234: Font tickLabelFont = getTickLabelFont();
1235: g2.setFont(tickLabelFont);
1236: if (isAutoTickUnitSelection()) {
1237: selectAutoTickUnit(g2, dataArea, edge);
1238: }
1239:
1240: double size = getTickUnit().getSize();
1241: int count = calculateVisibleTickCount();
1242: double lowestTickValue = calculateLowestVisibleTickValue();
1243:
1244: if (count <= ValueAxis.MAXIMUM_TICK_COUNT) {
1245: for (int i = 0; i < count; i++) {
1246: double currentTickValue = lowestTickValue + (i * size);
1247: String tickLabel;
1248: NumberFormat formatter = getNumberFormatOverride();
1249: if (formatter != null) {
1250: tickLabel = formatter.format(currentTickValue);
1251: } else {
1252: tickLabel = getTickUnit().valueToString(
1253: currentTickValue);
1254: }
1255:
1256: TextAnchor anchor = null;
1257: TextAnchor rotationAnchor = null;
1258: double angle = 0.0;
1259: if (isVerticalTickLabels()) {
1260: if (edge == RectangleEdge.LEFT) {
1261: anchor = TextAnchor.BOTTOM_CENTER;
1262: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1263: angle = -Math.PI / 2.0;
1264: } else {
1265: anchor = TextAnchor.BOTTOM_CENTER;
1266: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1267: angle = Math.PI / 2.0;
1268: }
1269: } else {
1270: if (edge == RectangleEdge.LEFT) {
1271: anchor = TextAnchor.CENTER_RIGHT;
1272: rotationAnchor = TextAnchor.CENTER_RIGHT;
1273: } else {
1274: anchor = TextAnchor.CENTER_LEFT;
1275: rotationAnchor = TextAnchor.CENTER_LEFT;
1276: }
1277: }
1278:
1279: Tick tick = new NumberTick(
1280: new Double(currentTickValue), tickLabel,
1281: anchor, rotationAnchor, angle);
1282: result.add(tick);
1283: }
1284: }
1285: return result;
1286:
1287: }
1288:
1289: /**
1290: * Returns a clone of the axis.
1291: *
1292: * @return A clone
1293: *
1294: * @throws CloneNotSupportedException if some component of the axis does
1295: * not support cloning.
1296: */
1297: public Object clone() throws CloneNotSupportedException {
1298: NumberAxis clone = (NumberAxis) super .clone();
1299: if (this .numberFormatOverride != null) {
1300: clone.numberFormatOverride = (NumberFormat) this .numberFormatOverride
1301: .clone();
1302: }
1303: return clone;
1304: }
1305:
1306: /**
1307: * Tests the axis for equality with an arbitrary object.
1308: *
1309: * @param obj the object (<code>null</code> permitted).
1310: *
1311: * @return A boolean.
1312: */
1313: public boolean equals(Object obj) {
1314: if (obj == this ) {
1315: return true;
1316: }
1317: if (!(obj instanceof NumberAxis)) {
1318: return false;
1319: }
1320: if (!super .equals(obj)) {
1321: return false;
1322: }
1323: NumberAxis that = (NumberAxis) obj;
1324: if (this .autoRangeIncludesZero != that.autoRangeIncludesZero) {
1325: return false;
1326: }
1327: if (this .autoRangeStickyZero != that.autoRangeStickyZero) {
1328: return false;
1329: }
1330: if (!ObjectUtilities.equal(this .tickUnit, that.tickUnit)) {
1331: return false;
1332: }
1333: if (!ObjectUtilities.equal(this .numberFormatOverride,
1334: that.numberFormatOverride)) {
1335: return false;
1336: }
1337: if (!this .rangeType.equals(that.rangeType)) {
1338: return false;
1339: }
1340: return true;
1341: }
1342:
1343: /**
1344: * Returns a hash code for this object.
1345: *
1346: * @return A hash code.
1347: */
1348: public int hashCode() {
1349: if (getLabel() != null) {
1350: return getLabel().hashCode();
1351: } else {
1352: return 0;
1353: }
1354: }
1355:
1356: }
|