001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, by Object Refinery Limited and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ---------- 028 * Range.java 029 * ---------- 030 * (C) Copyright 2002-2006, by Object Refinery Limited and Contributors. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): Chuanhao Chiu; 034 * Bill Kelemen; 035 * Nicolas Brodu; 036 * 037 * $Id: Range.java,v 1.6.2.2 2006/01/11 11:27:34 mungady Exp $ 038 * 039 * Changes (from 23-Jun-2001) 040 * -------------------------- 041 * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG); 042 * 30-Apr-2002 : Added getLength() and getCentralValue() methods. Changed 043 * argument check in constructor (DG); 044 * 13-Jun-2002 : Added contains(double) method (DG); 045 * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks 046 * to Chuanhao Chiu for reporting and fixing this (DG); 047 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 048 * 26-Mar-2003 : Implemented Serializable (DG); 049 * 14-Aug-2003 : Added equals() method (DG); 050 * 27-Aug-2003 : Added toString() method (BK); 051 * 11-Sep-2003 : Added Clone Support (NB); 052 * 23-Sep-2003 : Fixed Checkstyle issues (DG); 053 * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB); 054 * 05-May-2004 : Added constrain() and intersects() methods (DG); 055 * 18-May-2004 : Added expand() method (DG); 056 * ------------- JFreeChart 1.0.0 --------------------------------------------- 057 * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG); 058 * 059 */ 060 061 package org.jfree.data; 062 063 import java.io.Serializable; 064 065 /** 066 * Represents an immutable range of values. 067 */ 068 public strictfp class Range implements Serializable { 069 070 /** For serialization. */ 071 private static final long serialVersionUID = -906333695431863380L; 072 073 /** The lower bound of the range. */ 074 private double lower; 075 076 /** The upper bound of the range. */ 077 private double upper; 078 079 /** 080 * Creates a new range. 081 * 082 * @param lower the lower bound (must be <= upper bound). 083 * @param upper the upper bound (must be >= lower bound). 084 */ 085 public Range(double lower, double upper) { 086 if (lower > upper) { 087 String msg = "Range(double, double): require lower (" + lower 088 + ") <= upper (" + upper + ")."; 089 throw new IllegalArgumentException(msg); 090 } 091 this.lower = lower; 092 this.upper = upper; 093 } 094 095 /** 096 * Returns the lower bound for the range. 097 * 098 * @return The lower bound. 099 */ 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 }