Source for org.jfree.data.Range

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------
  28:  * Range.java
  29:  * ----------
  30:  * (C) Copyright 2002-2006, by Object Refinery Limited and Contributors.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   Chuanhao Chiu;
  34:  *                   Bill Kelemen; 
  35:  *                   Nicolas Brodu;
  36:  *
  37:  * $Id: Range.java,v 1.6.2.2 2006/01/11 11:27:34 mungady Exp $
  38:  *
  39:  * Changes (from 23-Jun-2001)
  40:  * --------------------------
  41:  * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG);
  42:  * 30-Apr-2002 : Added getLength() and getCentralValue() methods.  Changed
  43:  *               argument check in constructor (DG);
  44:  * 13-Jun-2002 : Added contains(double) method (DG);
  45:  * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks
  46:  *               to Chuanhao Chiu for reporting and fixing this (DG);
  47:  * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
  48:  * 26-Mar-2003 : Implemented Serializable (DG);
  49:  * 14-Aug-2003 : Added equals() method (DG);
  50:  * 27-Aug-2003 : Added toString() method (BK);
  51:  * 11-Sep-2003 : Added Clone Support (NB);
  52:  * 23-Sep-2003 : Fixed Checkstyle issues (DG);
  53:  * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB);
  54:  * 05-May-2004 : Added constrain() and intersects() methods (DG);
  55:  * 18-May-2004 : Added expand() method (DG);
  56:  * ------------- JFreeChart 1.0.0 ---------------------------------------------
  57:  * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG);
  58:  * 
  59:  */
  60: 
  61: package org.jfree.data;
  62: 
  63: import java.io.Serializable;
  64: 
  65: /**
  66:  * Represents an immutable range of values.
  67:  */
  68: public strictfp class Range implements Serializable {
  69: 
  70:     /** For serialization. */
  71:     private static final long serialVersionUID = -906333695431863380L;
  72:     
  73:     /** The lower bound of the range. */
  74:     private double lower;
  75: 
  76:     /** The upper bound of the range. */
  77:     private double upper;
  78: 
  79:     /**
  80:      * Creates a new range.
  81:      *
  82:      * @param lower  the lower bound (must be <= upper bound).
  83:      * @param upper  the upper bound (must be >= lower bound).
  84:      */
  85:     public Range(double lower, double upper) {
  86:         if (lower > upper) {
  87:             String msg = "Range(double, double): require lower (" + lower 
  88:                 + ") <= upper (" + upper + ").";
  89:             throw new IllegalArgumentException(msg);
  90:         }
  91:         this.lower = lower;
  92:         this.upper = upper;
  93:     }
  94: 
  95:     /**
  96:      * Returns the lower bound for the range.
  97:      *
  98:      * @return The lower bound.
  99:      */
 100:     public double getLowerBound() {
 101:         return this.lower;
 102:     }
 103: 
 104:     /**
 105:      * Returns the upper bound for the range.
 106:      *
 107:      * @return The upper bound.
 108:      */
 109:     public double getUpperBound() {
 110:         return this.upper;
 111:     }
 112: 
 113:     /**
 114:      * Returns the length of the range.
 115:      *
 116:      * @return The length.
 117:      */
 118:     public double getLength() {
 119:         return this.upper - this.lower;
 120:     }
 121: 
 122:     /**
 123:      * Returns the central value for the range.
 124:      *
 125:      * @return The central value.
 126:      */
 127:     public double getCentralValue() {
 128:         return this.lower / 2.0 + this.upper / 2.0;
 129:     }
 130: 
 131:     /**
 132:      * Returns <code>true</code> if the range contains the specified value and 
 133:      * <code>false</code> otherwise.
 134:      *
 135:      * @param value  the value to lookup.
 136:      *
 137:      * @return <code>true</code> if the range contains the specified value.
 138:      */
 139:     public boolean contains(double value) {
 140:         return (value >= this.lower && value <= this.upper);
 141:     }
 142:     
 143:     /**
 144:      * Returns <code>true</code> if the range intersects with the specified 
 145:      * range, and <code>false</code> otherwise.
 146:      * 
 147:      * @param b0  the lower bound (should be <= b1).
 148:      * @param b1  the upper bound (should be >= b0).
 149:      * 
 150:      * @return A boolean.
 151:      */
 152:     public boolean intersects(double b0, double b1) {
 153:         if (b0 <= this.lower) {
 154:             return (b1 > this.lower);
 155:         }
 156:         else {
 157:             return (b0 < this.upper && b1 >= b0);
 158:         }
 159:     }
 160: 
 161:     /**
 162:      * Returns the value within the range that is closest to the specified 
 163:      * value.
 164:      * 
 165:      * @param value  the value.
 166:      * 
 167:      * @return The constrained value.
 168:      */
 169:     public double constrain(double value) {
 170:         double result = value;
 171:         if (!contains(value)) {
 172:             if (value > this.upper) {
 173:                 result = this.upper;   
 174:             }
 175:             else if (value < this.lower) {
 176:                 result = this.lower;   
 177:             }
 178:         }
 179:         return result;
 180:     }
 181:     
 182:     /**
 183:      * Creates a new range by combining two existing ranges.
 184:      * <P>
 185:      * Note that:
 186:      * <ul>
 187:      *   <li>either range can be <code>null</code>, in which case the other 
 188:      *       range is returned;</li>
 189:      *   <li>if both ranges are <code>null</code> the return value is 
 190:      *       <code>null</code>.</li>
 191:      * </ul>
 192:      *
 193:      * @param range1  the first range (<code>null</code> permitted).
 194:      * @param range2  the second range (<code>null</code> permitted).
 195:      *
 196:      * @return A new range (possibly <code>null</code>).
 197:      */
 198:     public static Range combine(Range range1, Range range2) {
 199:         if (range1 == null) {
 200:             return range2;
 201:         }
 202:         else {
 203:             if (range2 == null) {
 204:                 return range1;
 205:             }
 206:             else {
 207:                 double l = Math.min(range1.getLowerBound(), 
 208:                         range2.getLowerBound());
 209:                 double u = Math.max(range1.getUpperBound(), 
 210:                         range2.getUpperBound());
 211:                 return new Range(l, u);
 212:             }
 213:         }
 214:     }
 215:     
 216:     /**
 217:      * Returns a range that includes all the values in the specified 
 218:      * <code>range</code> AND the specified <code>value</code>.
 219:      * 
 220:      * @param range  the range (<code>null</code> permitted).
 221:      * @param value  the value that must be included.
 222:      * 
 223:      * @return A range.
 224:      * 
 225:      * @since 1.0.1
 226:      */
 227:     public static Range expandToInclude(Range range, double value) {
 228:         if (range == null) {
 229:             return new Range(value, value);
 230:         }
 231:         if (value < range.getLowerBound()) {
 232:             return new Range(value, range.getUpperBound());
 233:         }
 234:         else if (value > range.getUpperBound()) {
 235:             return new Range(range.getLowerBound(), value);
 236:         }
 237:         else {
 238:             return range;
 239:         }
 240:     }
 241:     
 242:     /**
 243:      * Creates a new range by adding margins to an existing range.
 244:      * 
 245:      * @param range  the range (<code>null</code> not permitted).
 246:      * @param lowerMargin  the lower margin (expressed as a percentage of the 
 247:      *                     range length).
 248:      * @param upperMargin  the upper margin (expressed as a percentage of the 
 249:      *                     range length).
 250:      * 
 251:      * @return The expanded range.
 252:      */
 253:     public static Range expand(Range range, 
 254:                                double lowerMargin, double upperMargin) {
 255:         if (range == null) {
 256:             throw new IllegalArgumentException("Null 'range' argument.");   
 257:         }
 258:         double length = range.getLength();
 259:         double lower = length * lowerMargin;
 260:         double upper = length * upperMargin;
 261:         return new Range(range.getLowerBound() - lower, 
 262:                 range.getUpperBound() + upper);
 263:     }
 264: 
 265:     /**
 266:      * Shifts the range by the specified amount.
 267:      * 
 268:      * @param base  the base range.
 269:      * @param delta  the shift amount.
 270:      * 
 271:      * @return A new range.
 272:      */
 273:     public static Range shift(Range base, double delta) {
 274:         return shift(base, delta, false);
 275:     }
 276:     
 277:     /**
 278:      * Shifts the range by the specified amount.
 279:      * 
 280:      * @param base  the base range.
 281:      * @param delta  the shift amount.
 282:      * @param allowZeroCrossing  a flag that determines whether or not the 
 283:      *                           bounds of the range are allowed to cross
 284:      *                           zero after adjustment.
 285:      * 
 286:      * @return A new range.
 287:      */
 288:     public static Range shift(Range base, double delta, 
 289:                               boolean allowZeroCrossing) {
 290:         if (allowZeroCrossing) {
 291:             return new Range(base.getLowerBound() + delta, 
 292:                     base.getUpperBound() + delta);
 293:         }
 294:         else {
 295:             return new Range(shiftWithNoZeroCrossing(base.getLowerBound(), 
 296:                     delta), shiftWithNoZeroCrossing(base.getUpperBound(), 
 297:                     delta));
 298:         }
 299:     }
 300: 
 301:     /**
 302:      * Returns the given <code>value</code> adjusted by <code>delta</code> but
 303:      * with a check to prevent the result from crossing <code>0.0</code>.
 304:      * 
 305:      * @param value  the value.
 306:      * @param delta  the adjustment.
 307:      * 
 308:      * @return The adjusted value.
 309:      */
 310:     private static double shiftWithNoZeroCrossing(double value, double delta) {
 311:         if (value > 0.0) {
 312:             return Math.max(value + delta, 0.0);  
 313:         }
 314:         else if (value < 0.0) {
 315:             return Math.min(value + delta, 0.0);
 316:         }
 317:         else {
 318:             return value + delta;   
 319:         }
 320:     }
 321:     
 322:     /**
 323:      * Tests this object for equality with an arbitrary object.
 324:      *
 325:      * @param obj  the object to test against (<code>null</code> permitted).
 326:      *
 327:      * @return A boolean.
 328:      */
 329:     public boolean equals(Object obj) {
 330:         if (!(obj instanceof Range)) {
 331:             return false;
 332:         }
 333:         Range range = (Range) obj;
 334:         if (!(this.lower == range.lower)) {
 335:             return false;
 336:         }
 337:         if (!(this.upper == range.upper)) {
 338:             return false;
 339:         }
 340:         return true;
 341:     }
 342: 
 343:     /**
 344:      * Returns a hash code.
 345:      * 
 346:      * @return A hash code.
 347:      */
 348:     public int hashCode() {
 349:         int result;
 350:         long temp;
 351:         temp = Double.doubleToLongBits(this.lower);
 352:         result = (int) (temp ^ (temp >>> 32));
 353:         temp = Double.doubleToLongBits(this.upper);
 354:         result = 29 * result + (int) (temp ^ (temp >>> 32));
 355:         return result;
 356:     }
 357: 
 358:     /**
 359:      * Returns a string representation of this Range.
 360:      *
 361:      * @return A String "Range[lower,upper]" where lower=lower range and 
 362:      *         upper=upper range.
 363:      */
 364:     public String toString() {
 365:         return ("Range[" + this.lower + "," + this.upper + "]");
 366:     }
 367: 
 368: }