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: * LogarithmicAxis.java
0029: * --------------------
0030: * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
0031: *
0032: * Original Author: Michael Duffy / Eric Thomas;
0033: * Contributor(s): David Gilbert (for Object Refinery Limited);
0034: * David M. O'Donnell;
0035: * Scott Sams;
0036: * Sergei Ivanov;
0037: *
0038: * $Id: LogarithmicAxis.java,v 1.11.2.5 2007/03/22 12:13:27 mungady Exp $
0039: *
0040: * Changes
0041: * -------
0042: * 14-Mar-2002 : Version 1 contributed by Michael Duffy (DG);
0043: * 19-Apr-2002 : drawVerticalString() is now drawRotatedString() in
0044: * RefineryUtilities (DG);
0045: * 23-Apr-2002 : Added a range property (DG);
0046: * 15-May-2002 : Modified to be able to deal with negative and zero values (via
0047: * new 'adjustedLog10()' method); occurrences of "Math.log(10)"
0048: * changed to "LOG10_VALUE"; changed 'intValue()' to
0049: * 'longValue()' in 'refreshTicks()' to fix label-text value
0050: * out-of-range problem; removed 'draw()' method; added
0051: * 'autoRangeMinimumSize' check; added 'log10TickLabelsFlag'
0052: * parameter flag and implementation (ET);
0053: * 25-Jun-2002 : Removed redundant import (DG);
0054: * 25-Jul-2002 : Changed order of parameters in ValueAxis constructor (DG);
0055: * 16-Jul-2002 : Implemented support for plotting positive values arbitrarily
0056: * close to zero (added 'allowNegativesFlag' flag) (ET).
0057: * 05-Sep-2002 : Updated constructor reflecting changes in the Axis class (DG);
0058: * 02-Oct-2002 : Fixed errors reported by Checkstyle (DG);
0059: * 08-Nov-2002 : Moved to new package com.jrefinery.chart.axis (DG);
0060: * 22-Nov-2002 : Bug fixes from David M. O'Donnell (DG);
0061: * 14-Jan-2003 : Changed autoRangeMinimumSize from Number --> double (DG);
0062: * 20-Jan-2003 : Removed unnecessary constructors (DG);
0063: * 26-Mar-2003 : Implemented Serializable (DG);
0064: * 08-May-2003 : Fixed plotting of datasets with lower==upper bounds when
0065: * 'minAutoRange' is very small; added 'strictValuesFlag'
0066: * and default functionality of throwing a runtime exception
0067: * if 'allowNegativesFlag' is false and any values are less
0068: * than or equal to zero; added 'expTickLabelsFlag' and
0069: * changed to use "1e#"-style tick labels by default
0070: * ("10^n"-style tick labels still supported via 'set'
0071: * method); improved generation of tick labels when range of
0072: * values is small; changed to use 'NumberFormat.getInstance()'
0073: * to create 'numberFormatterObj' (ET);
0074: * 14-May-2003 : Merged HorizontalLogarithmicAxis and
0075: * VerticalLogarithmicAxis (DG);
0076: * 29-Oct-2003 : Added workaround for font alignment in PDF output (DG);
0077: * 07-Nov-2003 : Modified to use new NumberTick class (DG);
0078: * 08-Apr-2004 : Use numberFormatOverride if set - see patch 930139 (DG);
0079: * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
0080: * 21-Apr-2005 : Added support for upper and lower margins; added
0081: * get/setAutoRangeNextLogFlag() methods and changed
0082: * default to 'autoRangeNextLogFlag'==false (ET);
0083: * 22-Apr-2005 : Removed refreshTicks() and fixed names and parameters for
0084: * refreshHorizontalTicks() & refreshVerticalTicks();
0085: * changed javadoc on setExpTickLabelsFlag() to specify
0086: * proper default (ET);
0087: * 22-Apr-2005 : Renamed refreshHorizontalTicks --> refreshTicksHorizontal
0088: * (and likewise the vertical version) for consistency with
0089: * other axis classes (DG);
0090: * ------------- JFREECHART 1.0.x ---------------------------------------------
0091: * 02-Feb-2007 : Removed author tags all over JFreeChart sources (DG);
0092: * 02-Mar-2007 : Applied patch 1671069 to fix zooming (DG);
0093: * 22-Mar-2007 : Use new defaultAutoRange attribute (DG);
0094: *
0095: */
0096:
0097: package org.jfree.chart.axis;
0098:
0099: import java.awt.Graphics2D;
0100: import java.awt.geom.Rectangle2D;
0101: import java.text.DecimalFormat;
0102: import java.text.NumberFormat;
0103: import java.util.List;
0104:
0105: import org.jfree.chart.plot.Plot;
0106: import org.jfree.chart.plot.ValueAxisPlot;
0107: import org.jfree.data.Range;
0108: import org.jfree.ui.RectangleEdge;
0109: import org.jfree.ui.TextAnchor;
0110:
0111: /**
0112: * A numerical axis that uses a logarithmic scale.
0113: */
0114: public class LogarithmicAxis extends NumberAxis {
0115:
0116: /** For serialization. */
0117: private static final long serialVersionUID = 2502918599004103054L;
0118:
0119: /** Useful constant for log(10). */
0120: public static final double LOG10_VALUE = Math.log(10.0);
0121:
0122: /** Smallest arbitrarily-close-to-zero value allowed. */
0123: public static final double SMALL_LOG_VALUE = 1e-100;
0124:
0125: /** Flag set true to allow negative values in data. */
0126: protected boolean allowNegativesFlag = false;
0127:
0128: /**
0129: * Flag set true make axis throw exception if any values are
0130: * <= 0 and 'allowNegativesFlag' is false.
0131: */
0132: protected boolean strictValuesFlag = true;
0133:
0134: /** Number formatter for generating numeric strings. */
0135: protected final NumberFormat numberFormatterObj = NumberFormat
0136: .getInstance();
0137:
0138: /** Flag set true for "1e#"-style tick labels. */
0139: protected boolean expTickLabelsFlag = false;
0140:
0141: /** Flag set true for "10^n"-style tick labels. */
0142: protected boolean log10TickLabelsFlag = false;
0143:
0144: /** True to make 'autoAdjustRange()' select "10^n" values. */
0145: protected boolean autoRangeNextLogFlag = false;
0146:
0147: /** Helper flag for log axis processing. */
0148: protected boolean smallLogFlag = false;
0149:
0150: /**
0151: * Creates a new axis.
0152: *
0153: * @param label the axis label.
0154: */
0155: public LogarithmicAxis(String label) {
0156: super (label);
0157: setupNumberFmtObj(); //setup number formatter obj
0158: }
0159:
0160: /**
0161: * Sets the 'allowNegativesFlag' flag; true to allow negative values
0162: * in data, false to be able to plot positive values arbitrarily close to
0163: * zero.
0164: *
0165: * @param flgVal the new value of the flag.
0166: */
0167: public void setAllowNegativesFlag(boolean flgVal) {
0168: this .allowNegativesFlag = flgVal;
0169: }
0170:
0171: /**
0172: * Returns the 'allowNegativesFlag' flag; true to allow negative values
0173: * in data, false to be able to plot positive values arbitrarily close
0174: * to zero.
0175: *
0176: * @return The flag.
0177: */
0178: public boolean getAllowNegativesFlag() {
0179: return this .allowNegativesFlag;
0180: }
0181:
0182: /**
0183: * Sets the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
0184: * is false then this axis will throw a runtime exception if any of its
0185: * values are less than or equal to zero; if false then the axis will
0186: * adjust for values less than or equal to zero as needed.
0187: *
0188: * @param flgVal true for strict enforcement.
0189: */
0190: public void setStrictValuesFlag(boolean flgVal) {
0191: this .strictValuesFlag = flgVal;
0192: }
0193:
0194: /**
0195: * Returns the 'strictValuesFlag' flag; if true and 'allowNegativesFlag'
0196: * is false then this axis will throw a runtime exception if any of its
0197: * values are less than or equal to zero; if false then the axis will
0198: * adjust for values less than or equal to zero as needed.
0199: *
0200: * @return <code>true</code> if strict enforcement is enabled.
0201: */
0202: public boolean getStrictValuesFlag() {
0203: return this .strictValuesFlag;
0204: }
0205:
0206: /**
0207: * Sets the 'expTickLabelsFlag' flag. If the 'log10TickLabelsFlag'
0208: * is false then this will set whether or not "1e#"-style tick labels
0209: * are used. The default is to use regular numeric tick labels.
0210: *
0211: * @param flgVal true for "1e#"-style tick labels, false for
0212: * log10 or regular numeric tick labels.
0213: */
0214: public void setExpTickLabelsFlag(boolean flgVal) {
0215: this .expTickLabelsFlag = flgVal;
0216: setupNumberFmtObj(); //setup number formatter obj
0217: }
0218:
0219: /**
0220: * Returns the 'expTickLabelsFlag' flag.
0221: *
0222: * @return <code>true</code> for "1e#"-style tick labels,
0223: * <code>false</code> for log10 or regular numeric tick labels.
0224: */
0225: public boolean getExpTickLabelsFlag() {
0226: return this .expTickLabelsFlag;
0227: }
0228:
0229: /**
0230: * Sets the 'log10TickLabelsFlag' flag. The default value is false.
0231: *
0232: * @param flag true for "10^n"-style tick labels, false for "1e#"-style
0233: * or regular numeric tick labels.
0234: */
0235: public void setLog10TickLabelsFlag(boolean flag) {
0236: this .log10TickLabelsFlag = flag;
0237: }
0238:
0239: /**
0240: * Returns the 'log10TickLabelsFlag' flag.
0241: *
0242: * @return <code>true</code> for "10^n"-style tick labels,
0243: * <code>false</code> for "1e#"-style or regular numeric tick
0244: * labels.
0245: */
0246: public boolean getLog10TickLabelsFlag() {
0247: return this .log10TickLabelsFlag;
0248: }
0249:
0250: /**
0251: * Sets the 'autoRangeNextLogFlag' flag. This determines whether or
0252: * not the 'autoAdjustRange()' method will select the next "10^n"
0253: * values when determining the upper and lower bounds. The default
0254: * value is false.
0255: *
0256: * @param flag <code>true</code> to make the 'autoAdjustRange()'
0257: * method select the next "10^n" values, <code>false</code> to not.
0258: */
0259: public void setAutoRangeNextLogFlag(boolean flag) {
0260: this .autoRangeNextLogFlag = flag;
0261: }
0262:
0263: /**
0264: * Returns the 'autoRangeNextLogFlag' flag.
0265: *
0266: * @return <code>true</code> if the 'autoAdjustRange()' method will
0267: * select the next "10^n" values, <code>false</code> if not.
0268: */
0269: public boolean getAutoRangeNextLogFlag() {
0270: return this .autoRangeNextLogFlag;
0271: }
0272:
0273: /**
0274: * Overridden version that calls original and then sets up flag for
0275: * log axis processing.
0276: *
0277: * @param range the new range.
0278: */
0279: public void setRange(Range range) {
0280: super .setRange(range); // call parent method
0281: setupSmallLogFlag(); // setup flag based on bounds values
0282: }
0283:
0284: /**
0285: * Sets up flag for log axis processing. Set true if negative values
0286: * not allowed and the lower bound is between 0 and 10.
0287: */
0288: protected void setupSmallLogFlag() {
0289: // set flag true if negative values not allowed and the
0290: // lower bound is between 0 and 10:
0291: double lowerVal = getRange().getLowerBound();
0292: this .smallLogFlag = (!this .allowNegativesFlag
0293: && lowerVal < 10.0 && lowerVal > 0.0);
0294: }
0295:
0296: /**
0297: * Sets up the number formatter object according to the
0298: * 'expTickLabelsFlag' flag.
0299: */
0300: protected void setupNumberFmtObj() {
0301: if (this .numberFormatterObj instanceof DecimalFormat) {
0302: //setup for "1e#"-style tick labels or regular
0303: // numeric tick labels, depending on flag:
0304: ((DecimalFormat) this .numberFormatterObj)
0305: .applyPattern(this .expTickLabelsFlag ? "0E0"
0306: : "0.###");
0307: }
0308: }
0309:
0310: /**
0311: * Returns the log10 value, depending on if values between 0 and
0312: * 1 are being plotted. If negative values are not allowed and
0313: * the lower bound is between 0 and 10 then a normal log is
0314: * returned; otherwise the returned value is adjusted if the
0315: * given value is less than 10.
0316: *
0317: * @param val the value.
0318: *
0319: * @return log<sub>10</sub>(val).
0320: *
0321: * @see #switchedPow10(double)
0322: */
0323: protected double switchedLog10(double val) {
0324: return this .smallLogFlag ? Math.log(val) / LOG10_VALUE
0325: : adjustedLog10(val);
0326: }
0327:
0328: /**
0329: * Returns a power of 10, depending on if values between 0 and
0330: * 1 are being plotted. If negative values are not allowed and
0331: * the lower bound is between 0 and 10 then a normal power is
0332: * returned; otherwise the returned value is adjusted if the
0333: * given value is less than 1.
0334: *
0335: * @param val the value.
0336: *
0337: * @return 10<sup>val</sup>.
0338: *
0339: * @since 1.0.5
0340: * @see #switchedLog10(double)
0341: */
0342: public double switchedPow10(double val) {
0343: return this .smallLogFlag ? Math.pow(10.0, val)
0344: : adjustedPow10(val);
0345: }
0346:
0347: /**
0348: * Returns an adjusted log10 value for graphing purposes. The first
0349: * adjustment is that negative values are changed to positive during
0350: * the calculations, and then the answer is negated at the end. The
0351: * second is that, for values less than 10, an increasingly large
0352: * (0 to 1) scaling factor is added such that at 0 the value is
0353: * adjusted to 1, resulting in a returned result of 0.
0354: *
0355: * @param val value for which log10 should be calculated.
0356: *
0357: * @return An adjusted log<sub>10</sub>(val).
0358: *
0359: * @see #adjustedPow10(double)
0360: */
0361: public double adjustedLog10(double val) {
0362: boolean negFlag = (val < 0.0);
0363: if (negFlag) {
0364: val = -val; // if negative then set flag and make positive
0365: }
0366: if (val < 10.0) { // if < 10 then
0367: val += (10.0 - val) / 10.0; //increase so 0 translates to 0
0368: }
0369: //return value; negate if original value was negative:
0370: double res = Math.log(val) / LOG10_VALUE;
0371: return negFlag ? (-res) : res;
0372: }
0373:
0374: /**
0375: * Returns an adjusted power of 10 value for graphing purposes. The first
0376: * adjustment is that negative values are changed to positive during
0377: * the calculations, and then the answer is negated at the end. The
0378: * second is that, for values less than 1, a progressive logarithmic
0379: * offset is subtracted such that at 0 the returned result is also 0.
0380: *
0381: * @param val value for which power of 10 should be calculated.
0382: *
0383: * @return An adjusted 10<sup>val</sup>.
0384: *
0385: * @since 1.0.5
0386: * @see #adjustedLog10(double)
0387: */
0388: public double adjustedPow10(double val) {
0389: boolean negFlag = (val < 0.0);
0390: if (negFlag) {
0391: val = -val; // if negative then set flag and make positive
0392: }
0393: double res;
0394: if (val < 1.0) {
0395: res = (Math.pow(10, val + 1.0) - 10.0) / 9.0; //invert adjustLog10
0396: } else {
0397: res = Math.pow(10, val);
0398: }
0399: return negFlag ? (-res) : res;
0400: }
0401:
0402: /**
0403: * Returns the largest (closest to positive infinity) double value that is
0404: * not greater than the argument, is equal to a mathematical integer and
0405: * satisfying the condition that log base 10 of the value is an integer
0406: * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
0407: *
0408: * @param lower a double value below which a floor will be calcualted.
0409: *
0410: * @return 10<sup>N</sup> with N .. { 1 ... }
0411: */
0412: protected double computeLogFloor(double lower) {
0413:
0414: double logFloor;
0415: if (this .allowNegativesFlag) {
0416: //negative values are allowed
0417: if (lower > 10.0) { //parameter value is > 10
0418: // The Math.log() function is based on e not 10.
0419: logFloor = Math.log(lower) / LOG10_VALUE;
0420: logFloor = Math.floor(logFloor);
0421: logFloor = Math.pow(10, logFloor);
0422: } else if (lower < -10.0) { //parameter value is < -10
0423: //calculate log using positive value:
0424: logFloor = Math.log(-lower) / LOG10_VALUE;
0425: //calculate floor using negative value:
0426: logFloor = Math.floor(-logFloor);
0427: //calculate power using positive value; then negate
0428: logFloor = -Math.pow(10, -logFloor);
0429: } else {
0430: //parameter value is -10 > val < 10
0431: logFloor = Math.floor(lower); //use as-is
0432: }
0433: } else {
0434: //negative values not allowed
0435: if (lower > 0.0) { //parameter value is > 0
0436: // The Math.log() function is based on e not 10.
0437: logFloor = Math.log(lower) / LOG10_VALUE;
0438: logFloor = Math.floor(logFloor);
0439: logFloor = Math.pow(10, logFloor);
0440: } else {
0441: //parameter value is <= 0
0442: logFloor = Math.floor(lower); //use as-is
0443: }
0444: }
0445: return logFloor;
0446: }
0447:
0448: /**
0449: * Returns the smallest (closest to negative infinity) double value that is
0450: * not less than the argument, is equal to a mathematical integer and
0451: * satisfying the condition that log base 10 of the value is an integer
0452: * (i.e., the value returned will be a power of 10: 1, 10, 100, 1000, etc.).
0453: *
0454: * @param upper a double value above which a ceiling will be calcualted.
0455: *
0456: * @return 10<sup>N</sup> with N .. { 1 ... }
0457: */
0458: protected double computeLogCeil(double upper) {
0459:
0460: double logCeil;
0461: if (this .allowNegativesFlag) {
0462: //negative values are allowed
0463: if (upper > 10.0) {
0464: //parameter value is > 10
0465: // The Math.log() function is based on e not 10.
0466: logCeil = Math.log(upper) / LOG10_VALUE;
0467: logCeil = Math.ceil(logCeil);
0468: logCeil = Math.pow(10, logCeil);
0469: } else if (upper < -10.0) {
0470: //parameter value is < -10
0471: //calculate log using positive value:
0472: logCeil = Math.log(-upper) / LOG10_VALUE;
0473: //calculate ceil using negative value:
0474: logCeil = Math.ceil(-logCeil);
0475: //calculate power using positive value; then negate
0476: logCeil = -Math.pow(10, -logCeil);
0477: } else {
0478: //parameter value is -10 > val < 10
0479: logCeil = Math.ceil(upper); //use as-is
0480: }
0481: } else {
0482: //negative values not allowed
0483: if (upper > 0.0) {
0484: //parameter value is > 0
0485: // The Math.log() function is based on e not 10.
0486: logCeil = Math.log(upper) / LOG10_VALUE;
0487: logCeil = Math.ceil(logCeil);
0488: logCeil = Math.pow(10, logCeil);
0489: } else {
0490: //parameter value is <= 0
0491: logCeil = Math.ceil(upper); //use as-is
0492: }
0493: }
0494: return logCeil;
0495: }
0496:
0497: /**
0498: * Rescales the axis to ensure that all data is visible.
0499: */
0500: public void autoAdjustRange() {
0501:
0502: Plot plot = getPlot();
0503: if (plot == null) {
0504: return; // no plot, no data.
0505: }
0506:
0507: if (plot instanceof ValueAxisPlot) {
0508: ValueAxisPlot vap = (ValueAxisPlot) plot;
0509:
0510: double lower;
0511: Range r = vap.getDataRange(this );
0512: if (r == null) {
0513: //no real data present
0514: r = getDefaultAutoRange();
0515: lower = r.getLowerBound(); //get lower bound value
0516: } else {
0517: //actual data is present
0518: lower = r.getLowerBound(); //get lower bound value
0519: if (this .strictValuesFlag && !this .allowNegativesFlag
0520: && lower <= 0.0) {
0521: //strict flag set, allow-negatives not set and values <= 0
0522: throw new RuntimeException(
0523: "Values less than or equal to "
0524: + "zero not allowed with logarithmic axis");
0525: }
0526: }
0527:
0528: //apply lower margin by decreasing lower bound:
0529: final double lowerMargin;
0530: if (lower > 0.0 && (lowerMargin = getLowerMargin()) > 0.0) {
0531: //lower bound and margin OK; get log10 of lower bound
0532: final double logLower = (Math.log(lower) / LOG10_VALUE);
0533: double logAbs; //get absolute value of log10 value
0534: if ((logAbs = Math.abs(logLower)) < 1.0) {
0535: logAbs = 1.0; //if less than 1.0 then make it 1.0
0536: } //subtract out margin and get exponential value:
0537: lower = Math.pow(10,
0538: (logLower - (logAbs * lowerMargin)));
0539: }
0540:
0541: //if flag then change to log version of lowest value
0542: // to make range begin at a 10^n value:
0543: if (this .autoRangeNextLogFlag) {
0544: lower = computeLogFloor(lower);
0545: }
0546:
0547: if (!this .allowNegativesFlag && lower >= 0.0
0548: && lower < SMALL_LOG_VALUE) {
0549: //negatives not allowed and lower range bound is zero
0550: lower = r.getLowerBound(); //use data range bound instead
0551: }
0552:
0553: double upper = r.getUpperBound();
0554:
0555: //apply upper margin by increasing upper bound:
0556: final double upperMargin;
0557: if (upper > 0.0 && (upperMargin = getUpperMargin()) > 0.0) {
0558: //upper bound and margin OK; get log10 of upper bound
0559: final double logUpper = (Math.log(upper) / LOG10_VALUE);
0560: double logAbs; //get absolute value of log10 value
0561: if ((logAbs = Math.abs(logUpper)) < 1.0) {
0562: logAbs = 1.0; //if less than 1.0 then make it 1.0
0563: } //add in margin and get exponential value:
0564: upper = Math.pow(10,
0565: (logUpper + (logAbs * upperMargin)));
0566: }
0567:
0568: if (!this .allowNegativesFlag && upper < 1.0 && upper > 0.0
0569: && lower > 0.0) {
0570: //negatives not allowed and upper bound between 0 & 1
0571: //round up to nearest significant digit for bound:
0572: //get negative exponent:
0573: double expVal = Math.log(upper) / LOG10_VALUE;
0574: expVal = Math.ceil(-expVal + 0.001); //get positive exponent
0575: expVal = Math.pow(10, expVal); //create multiplier value
0576: //multiply, round up, and divide for bound value:
0577: upper = (expVal > 0.0) ? Math.ceil(upper * expVal)
0578: / expVal : Math.ceil(upper);
0579: } else {
0580: //negatives allowed or upper bound not between 0 & 1
0581: //if flag then change to log version of highest value to
0582: // make range begin at a 10^n value; else use nearest int
0583: upper = (this .autoRangeNextLogFlag) ? computeLogCeil(upper)
0584: : Math.ceil(upper);
0585: }
0586: // ensure the autorange is at least <minRange> in size...
0587: double minRange = getAutoRangeMinimumSize();
0588: if (upper - lower < minRange) {
0589: upper = (upper + lower + minRange) / 2;
0590: lower = (upper + lower - minRange) / 2;
0591: //if autorange still below minimum then adjust by 1%
0592: // (can be needed when minRange is very small):
0593: if (upper - lower < minRange) {
0594: double absUpper = Math.abs(upper);
0595: //need to account for case where upper==0.0
0596: double adjVal = (absUpper > SMALL_LOG_VALUE) ? absUpper / 100.0
0597: : 0.01;
0598: upper = (upper + lower + adjVal) / 2;
0599: lower = (upper + lower - adjVal) / 2;
0600: }
0601: }
0602:
0603: setRange(new Range(lower, upper), false, false);
0604: setupSmallLogFlag(); //setup flag based on bounds values
0605: }
0606: }
0607:
0608: /**
0609: * Converts a data value to a coordinate in Java2D space, assuming that
0610: * the axis runs along one edge of the specified plotArea.
0611: * Note that it is possible for the coordinate to fall outside the
0612: * plotArea.
0613: *
0614: * @param value the data value.
0615: * @param plotArea the area for plotting the data.
0616: * @param edge the axis location.
0617: *
0618: * @return The Java2D coordinate.
0619: */
0620: public double valueToJava2D(double value, Rectangle2D plotArea,
0621: RectangleEdge edge) {
0622:
0623: Range range = getRange();
0624: double axisMin = switchedLog10(range.getLowerBound());
0625: double axisMax = switchedLog10(range.getUpperBound());
0626:
0627: double min = 0.0;
0628: double max = 0.0;
0629: if (RectangleEdge.isTopOrBottom(edge)) {
0630: min = plotArea.getMinX();
0631: max = plotArea.getMaxX();
0632: } else if (RectangleEdge.isLeftOrRight(edge)) {
0633: min = plotArea.getMaxY();
0634: max = plotArea.getMinY();
0635: }
0636:
0637: value = switchedLog10(value);
0638:
0639: if (isInverted()) {
0640: return max
0641: - (((value - axisMin) / (axisMax - axisMin)) * (max - min));
0642: } else {
0643: return min
0644: + (((value - axisMin) / (axisMax - axisMin)) * (max - min));
0645: }
0646:
0647: }
0648:
0649: /**
0650: * Converts a coordinate in Java2D space to the corresponding data
0651: * value, assuming that the axis runs along one edge of the specified
0652: * plotArea.
0653: *
0654: * @param java2DValue the coordinate in Java2D space.
0655: * @param plotArea the area in which the data is plotted.
0656: * @param edge the axis location.
0657: *
0658: * @return The data value.
0659: */
0660: public double java2DToValue(double java2DValue,
0661: Rectangle2D plotArea, RectangleEdge edge) {
0662:
0663: Range range = getRange();
0664: double axisMin = switchedLog10(range.getLowerBound());
0665: double axisMax = switchedLog10(range.getUpperBound());
0666:
0667: double plotMin = 0.0;
0668: double plotMax = 0.0;
0669: if (RectangleEdge.isTopOrBottom(edge)) {
0670: plotMin = plotArea.getX();
0671: plotMax = plotArea.getMaxX();
0672: } else if (RectangleEdge.isLeftOrRight(edge)) {
0673: plotMin = plotArea.getMaxY();
0674: plotMax = plotArea.getMinY();
0675: }
0676:
0677: if (isInverted()) {
0678: return switchedPow10(axisMax
0679: - ((java2DValue - plotMin) / (plotMax - plotMin))
0680: * (axisMax - axisMin));
0681: } else {
0682: return switchedPow10(axisMin
0683: + ((java2DValue - plotMin) / (plotMax - plotMin))
0684: * (axisMax - axisMin));
0685: }
0686: }
0687:
0688: /**
0689: * Zooms in on the current range.
0690: *
0691: * @param lowerPercent the new lower bound.
0692: * @param upperPercent the new upper bound.
0693: */
0694: public void zoomRange(double lowerPercent, double upperPercent) {
0695: double startLog = switchedLog10(getRange().getLowerBound());
0696: double lengthLog = switchedLog10(getRange().getUpperBound())
0697: - startLog;
0698: Range adjusted;
0699:
0700: if (isInverted()) {
0701: adjusted = new Range(switchedPow10(startLog
0702: + (lengthLog * (1 - upperPercent))),
0703: switchedPow10(startLog
0704: + (lengthLog * (1 - lowerPercent))));
0705: } else {
0706: adjusted = new Range(
0707: switchedPow10(startLog + (lengthLog * lowerPercent)),
0708: switchedPow10(startLog + (lengthLog * upperPercent)));
0709: }
0710:
0711: setRange(adjusted);
0712: }
0713:
0714: /**
0715: * Calculates the positions of the tick labels for the axis, storing the
0716: * results in the tick label list (ready for drawing).
0717: *
0718: * @param g2 the graphics device.
0719: * @param dataArea the area in which the plot should be drawn.
0720: * @param edge the location of the axis.
0721: *
0722: * @return A list of ticks.
0723: */
0724: protected List refreshTicksHorizontal(Graphics2D g2,
0725: Rectangle2D dataArea, RectangleEdge edge) {
0726:
0727: List ticks = new java.util.ArrayList();
0728: Range range = getRange();
0729:
0730: //get lower bound value:
0731: double lowerBoundVal = range.getLowerBound();
0732: //if small log values and lower bound value too small
0733: // then set to a small value (don't allow <= 0):
0734: if (this .smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
0735: lowerBoundVal = SMALL_LOG_VALUE;
0736: }
0737:
0738: //get upper bound value
0739: double upperBoundVal = range.getUpperBound();
0740:
0741: //get log10 version of lower bound and round to integer:
0742: int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
0743: //get log10 version of upper bound and round to integer:
0744: int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
0745:
0746: if (iBegCount == iEndCount && iBegCount > 0
0747: && Math.pow(10, iBegCount) > lowerBoundVal) {
0748: //only 1 power of 10 value, it's > 0 and its resulting
0749: // tick value will be larger than lower bound of data
0750: --iBegCount; //decrement to generate more ticks
0751: }
0752:
0753: double currentTickValue;
0754: String tickLabel;
0755: boolean zeroTickFlag = false;
0756: for (int i = iBegCount; i <= iEndCount; i++) {
0757: //for each power of 10 value; create ten ticks
0758: for (int j = 0; j < 10; ++j) {
0759: //for each tick to be displayed
0760: if (this .smallLogFlag) {
0761: //small log values in use; create numeric value for tick
0762: currentTickValue = Math.pow(10, i)
0763: + (Math.pow(10, i) * j);
0764: if (this .expTickLabelsFlag
0765: || (i < 0 && currentTickValue > 0.0 && currentTickValue < 1.0)) {
0766: //showing "1e#"-style ticks or negative exponent
0767: // generating tick value between 0 & 1; show fewer
0768: if (j == 0 || (i > -4 && j < 2)
0769: || currentTickValue >= upperBoundVal) {
0770: //first tick of series, or not too small a value and
0771: // one of first 3 ticks, or last tick to be displayed
0772: // set exact number of fractional digits to be shown
0773: // (no effect if showing "1e#"-style ticks):
0774: this .numberFormatterObj
0775: .setMaximumFractionDigits(-i);
0776: //create tick label (force use of fmt obj):
0777: tickLabel = makeTickLabel(currentTickValue,
0778: true);
0779: } else { //no tick label to be shown
0780: tickLabel = "";
0781: }
0782: } else { //tick value not between 0 & 1
0783: //show tick label if it's the first or last in
0784: // the set, or if it's 1-5; beyond that show
0785: // fewer as the values get larger:
0786: tickLabel = (j < 1 || (i < 1 && j < 5)
0787: || (j < 4 - i) || currentTickValue >= upperBoundVal) ? makeTickLabel(currentTickValue)
0788: : "";
0789: }
0790: } else { //not small log values in use; allow for values <= 0
0791: if (zeroTickFlag) { //if did zero tick last iter then
0792: --j; //decrement to do 1.0 tick now
0793: } //calculate power-of-ten value for tick:
0794: currentTickValue = (i >= 0) ? Math.pow(10, i)
0795: + (Math.pow(10, i) * j) : -(Math
0796: .pow(10, -i) - (Math.pow(10, -i - 1) * j));
0797: if (!zeroTickFlag) { // did not do zero tick last iteration
0798: if (Math.abs(currentTickValue - 1.0) < 0.0001
0799: && lowerBoundVal <= 0.0
0800: && upperBoundVal >= 0.0) {
0801: //tick value is 1.0 and 0.0 is within data range
0802: currentTickValue = 0.0; //set tick value to zero
0803: zeroTickFlag = true; //indicate zero tick
0804: }
0805: } else { //did zero tick last iteration
0806: zeroTickFlag = false; //clear flag
0807: } //create tick label string:
0808: //show tick label if "1e#"-style and it's one
0809: // of the first two, if it's the first or last
0810: // in the set, or if it's 1-5; beyond that
0811: // show fewer as the values get larger:
0812: tickLabel = ((this .expTickLabelsFlag && j < 2)
0813: || j < 1 || (i < 1 && j < 5) || (j < 4 - i) || currentTickValue >= upperBoundVal) ? makeTickLabel(currentTickValue)
0814: : "";
0815: }
0816:
0817: if (currentTickValue > upperBoundVal) {
0818: return ticks; // if past highest data value then exit
0819: // method
0820: }
0821:
0822: if (currentTickValue >= lowerBoundVal - SMALL_LOG_VALUE) {
0823: //tick value not below lowest data value
0824: TextAnchor anchor = null;
0825: TextAnchor rotationAnchor = null;
0826: double angle = 0.0;
0827: if (isVerticalTickLabels()) {
0828: anchor = TextAnchor.CENTER_RIGHT;
0829: rotationAnchor = TextAnchor.CENTER_RIGHT;
0830: if (edge == RectangleEdge.TOP) {
0831: angle = Math.PI / 2.0;
0832: } else {
0833: angle = -Math.PI / 2.0;
0834: }
0835: } else {
0836: if (edge == RectangleEdge.TOP) {
0837: anchor = TextAnchor.BOTTOM_CENTER;
0838: rotationAnchor = TextAnchor.BOTTOM_CENTER;
0839: } else {
0840: anchor = TextAnchor.TOP_CENTER;
0841: rotationAnchor = TextAnchor.TOP_CENTER;
0842: }
0843: }
0844:
0845: Tick tick = new NumberTick(new Double(
0846: currentTickValue), tickLabel, anchor,
0847: rotationAnchor, angle);
0848: ticks.add(tick);
0849: }
0850: }
0851: }
0852: return ticks;
0853:
0854: }
0855:
0856: /**
0857: * Calculates the positions of the tick labels for the axis, storing the
0858: * results in the tick label list (ready for drawing).
0859: *
0860: * @param g2 the graphics device.
0861: * @param dataArea the area in which the plot should be drawn.
0862: * @param edge the location of the axis.
0863: *
0864: * @return A list of ticks.
0865: */
0866: protected List refreshTicksVertical(Graphics2D g2,
0867: Rectangle2D dataArea, RectangleEdge edge) {
0868:
0869: List ticks = new java.util.ArrayList();
0870:
0871: //get lower bound value:
0872: double lowerBoundVal = getRange().getLowerBound();
0873: //if small log values and lower bound value too small
0874: // then set to a small value (don't allow <= 0):
0875: if (this .smallLogFlag && lowerBoundVal < SMALL_LOG_VALUE) {
0876: lowerBoundVal = SMALL_LOG_VALUE;
0877: }
0878: //get upper bound value
0879: double upperBoundVal = getRange().getUpperBound();
0880:
0881: //get log10 version of lower bound and round to integer:
0882: int iBegCount = (int) Math.rint(switchedLog10(lowerBoundVal));
0883: //get log10 version of upper bound and round to integer:
0884: int iEndCount = (int) Math.rint(switchedLog10(upperBoundVal));
0885:
0886: if (iBegCount == iEndCount && iBegCount > 0
0887: && Math.pow(10, iBegCount) > lowerBoundVal) {
0888: //only 1 power of 10 value, it's > 0 and its resulting
0889: // tick value will be larger than lower bound of data
0890: --iBegCount; //decrement to generate more ticks
0891: }
0892:
0893: double tickVal;
0894: String tickLabel;
0895: boolean zeroTickFlag = false;
0896: for (int i = iBegCount; i <= iEndCount; i++) {
0897: //for each tick with a label to be displayed
0898: int jEndCount = 10;
0899: if (i == iEndCount) {
0900: jEndCount = 1;
0901: }
0902:
0903: for (int j = 0; j < jEndCount; j++) {
0904: //for each tick to be displayed
0905: if (this .smallLogFlag) {
0906: //small log values in use
0907: tickVal = Math.pow(10, i) + (Math.pow(10, i) * j);
0908: if (j == 0) {
0909: //first tick of group; create label text
0910: if (this .log10TickLabelsFlag) {
0911: //if flag then
0912: tickLabel = "10^" + i; //create "log10"-type label
0913: } else { //not "log10"-type label
0914: if (this .expTickLabelsFlag) {
0915: //if flag then
0916: tickLabel = "1e" + i; //create "1e#"-type label
0917: } else { //not "1e#"-type label
0918: if (i >= 0) { // if positive exponent then
0919: // make integer
0920: NumberFormat format = getNumberFormatOverride();
0921: if (format != null) {
0922: tickLabel = format
0923: .format(tickVal);
0924: } else {
0925: tickLabel = Long
0926: .toString((long) Math
0927: .rint(tickVal));
0928: }
0929: } else {
0930: //negative exponent; create fractional value
0931: //set exact number of fractional digits to
0932: // be shown:
0933: this .numberFormatterObj
0934: .setMaximumFractionDigits(-i);
0935: //create tick label:
0936: tickLabel = this .numberFormatterObj
0937: .format(tickVal);
0938: }
0939: }
0940: }
0941: } else { //not first tick to be displayed
0942: tickLabel = ""; //no tick label
0943: }
0944: } else { //not small log values in use; allow for values <= 0
0945: if (zeroTickFlag) { //if did zero tick last iter then
0946: --j;
0947: } //decrement to do 1.0 tick now
0948: tickVal = (i >= 0) ? Math.pow(10, i)
0949: + (Math.pow(10, i) * j) : -(Math
0950: .pow(10, -i) - (Math.pow(10, -i - 1) * j));
0951: if (j == 0) { //first tick of group
0952: if (!zeroTickFlag) { // did not do zero tick last
0953: // iteration
0954: if (i > iBegCount && i < iEndCount
0955: && Math.abs(tickVal - 1.0) < 0.0001) {
0956: // not first or last tick on graph and value
0957: // is 1.0
0958: tickVal = 0.0; //change value to 0.0
0959: zeroTickFlag = true; //indicate zero tick
0960: tickLabel = "0"; //create label for tick
0961: } else {
0962: //first or last tick on graph or value is 1.0
0963: //create label for tick:
0964: if (this .log10TickLabelsFlag) {
0965: //create "log10"-type label
0966: tickLabel = (((i < 0) ? "-" : "")
0967: + "10^" + Math.abs(i));
0968: } else {
0969: if (this .expTickLabelsFlag) {
0970: //create "1e#"-type label
0971: tickLabel = (((i < 0) ? "-"
0972: : "")
0973: + "1e" + Math.abs(i));
0974: } else {
0975: NumberFormat format = getNumberFormatOverride();
0976: if (format != null) {
0977: tickLabel = format
0978: .format(tickVal);
0979: } else {
0980: tickLabel = Long
0981: .toString((long) Math
0982: .rint(tickVal));
0983: }
0984: }
0985: }
0986: }
0987: } else { // did zero tick last iteration
0988: tickLabel = ""; //no label
0989: zeroTickFlag = false; //clear flag
0990: }
0991: } else { // not first tick of group
0992: tickLabel = ""; //no label
0993: zeroTickFlag = false; //make sure flag cleared
0994: }
0995: }
0996:
0997: if (tickVal > upperBoundVal) {
0998: return ticks; //if past highest data value then exit method
0999: }
1000:
1001: if (tickVal >= lowerBoundVal - SMALL_LOG_VALUE) {
1002: //tick value not below lowest data value
1003: TextAnchor anchor = null;
1004: TextAnchor rotationAnchor = null;
1005: double angle = 0.0;
1006: if (isVerticalTickLabels()) {
1007: if (edge == RectangleEdge.LEFT) {
1008: anchor = TextAnchor.BOTTOM_CENTER;
1009: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1010: angle = -Math.PI / 2.0;
1011: } else {
1012: anchor = TextAnchor.BOTTOM_CENTER;
1013: rotationAnchor = TextAnchor.BOTTOM_CENTER;
1014: angle = Math.PI / 2.0;
1015: }
1016: } else {
1017: if (edge == RectangleEdge.LEFT) {
1018: anchor = TextAnchor.CENTER_RIGHT;
1019: rotationAnchor = TextAnchor.CENTER_RIGHT;
1020: } else {
1021: anchor = TextAnchor.CENTER_LEFT;
1022: rotationAnchor = TextAnchor.CENTER_LEFT;
1023: }
1024: }
1025: //create tick object and add to list:
1026: ticks.add(new NumberTick(new Double(tickVal),
1027: tickLabel, anchor, rotationAnchor, angle));
1028: }
1029: }
1030: }
1031: return ticks;
1032: }
1033:
1034: /**
1035: * Converts the given value to a tick label string.
1036: *
1037: * @param val the value to convert.
1038: * @param forceFmtFlag true to force the number-formatter object
1039: * to be used.
1040: *
1041: * @return The tick label string.
1042: */
1043: protected String makeTickLabel(double val, boolean forceFmtFlag) {
1044: if (this .expTickLabelsFlag || forceFmtFlag) {
1045: //using exponents or force-formatter flag is set
1046: // (convert 'E' to lower-case 'e'):
1047: return this .numberFormatterObj.format(val).toLowerCase();
1048: }
1049: return getTickUnit().valueToString(val);
1050: }
1051:
1052: /**
1053: * Converts the given value to a tick label string.
1054: * @param val the value to convert.
1055: *
1056: * @return The tick label string.
1057: */
1058: protected String makeTickLabel(double val) {
1059: return makeTickLabel(val, false);
1060: }
1061:
1062: }
|