001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2008, 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-2008, 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 * Sergei Ivanov; 037 * 038 * Changes (from 23-Jun-2001) 039 * -------------------------- 040 * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG); 041 * 30-Apr-2002 : Added getLength() and getCentralValue() methods. Changed 042 * argument check in constructor (DG); 043 * 13-Jun-2002 : Added contains(double) method (DG); 044 * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks 045 * to Chuanhao Chiu for reporting and fixing this (DG); 046 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG); 047 * 26-Mar-2003 : Implemented Serializable (DG); 048 * 14-Aug-2003 : Added equals() method (DG); 049 * 27-Aug-2003 : Added toString() method (BK); 050 * 11-Sep-2003 : Added Clone Support (NB); 051 * 23-Sep-2003 : Fixed Checkstyle issues (DG); 052 * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB); 053 * 05-May-2004 : Added constrain() and intersects() methods (DG); 054 * 18-May-2004 : Added expand() method (DG); 055 * ------------- JFreeChart 1.0.x --------------------------------------------- 056 * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG); 057 * 18-Dec-2007 : New methods intersects(Range) and scale(...) thanks to Sergei 058 * Ivanov (DG); 059 * 060 */ 061 062 package org.jfree.data; 063 064 import java.io.Serializable; 065 066 /** 067 * Represents an immutable range of values. 068 */ 069 public strictfp class Range implements Serializable { 070 071 /** For serialization. */ 072 private static final long serialVersionUID = -906333695431863380L; 073 074 /** The lower bound of the range. */ 075 private double lower; 076 077 /** The upper bound of the range. */ 078 private double upper; 079 080 /** 081 * Creates a new range. 082 * 083 * @param lower the lower bound (must be <= upper bound). 084 * @param upper the upper bound (must be >= lower bound). 085 */ 086 public Range(double lower, double upper) { 087 if (lower > upper) { 088 String msg = "Range(double, double): require lower (" + lower 089 + ") <= upper (" + upper + ")."; 090 throw new IllegalArgumentException(msg); 091 } 092 this.lower = lower; 093 this.upper = upper; 094 } 095 096 /** 097 * Returns the lower bound for the range. 098 * 099 * @return The lower bound. 100 */ 101 public double getLowerBound() { 102 return this.lower; 103 } 104 105 /** 106 * Returns the upper bound for the range. 107 * 108 * @return The upper bound. 109 */ 110 public double getUpperBound() { 111 return this.upper; 112 } 113 114 /** 115 * Returns the length of the range. 116 * 117 * @return The length. 118 */ 119 public double getLength() { 120 return this.upper - this.lower; 121 } 122 123 /** 124 * Returns the central value for the range. 125 * 126 * @return The central value. 127 */ 128 public double getCentralValue() { 129 return this.lower / 2.0 + this.upper / 2.0; 130 } 131 132 /** 133 * Returns <code>true</code> if the range contains the specified value and 134 * <code>false</code> otherwise. 135 * 136 * @param value the value to lookup. 137 * 138 * @return <code>true</code> if the range contains the specified value. 139 */ 140 public boolean contains(double value) { 141 return (value >= this.lower && value <= this.upper); 142 } 143 144 /** 145 * Returns <code>true</code> if the range intersects with the specified 146 * range, and <code>false</code> otherwise. 147 * 148 * @param b0 the lower bound (should be <= b1). 149 * @param b1 the upper bound (should be >= b0). 150 * 151 * @return A boolean. 152 */ 153 public boolean intersects(double b0, double b1) { 154 if (b0 <= this.lower) { 155 return (b1 > this.lower); 156 } 157 else { 158 return (b0 < this.upper && b1 >= b0); 159 } 160 } 161 162 /** 163 * Returns <code>true</code> if the range intersects with the specified 164 * range, and <code>false</code> otherwise. 165 * 166 * @param range another range (<code>null</code> not permitted). 167 * 168 * @return A boolean. 169 * 170 * @since 1.0.9 171 */ 172 public boolean intersects(Range range) { 173 return intersects(range.getLowerBound(), range.getUpperBound()); 174 } 175 176 /** 177 * Returns the value within the range that is closest to the specified 178 * value. 179 * 180 * @param value the value. 181 * 182 * @return The constrained value. 183 */ 184 public double constrain(double value) { 185 double result = value; 186 if (!contains(value)) { 187 if (value > this.upper) { 188 result = this.upper; 189 } 190 else if (value < this.lower) { 191 result = this.lower; 192 } 193 } 194 return result; 195 } 196 197 /** 198 * Creates a new range by combining two existing ranges. 199 * <P> 200 * Note that: 201 * <ul> 202 * <li>either range can be <code>null</code>, in which case the other 203 * range is returned;</li> 204 * <li>if both ranges are <code>null</code> the return value is 205 * <code>null</code>.</li> 206 * </ul> 207 * 208 * @param range1 the first range (<code>null</code> permitted). 209 * @param range2 the second range (<code>null</code> permitted). 210 * 211 * @return A new range (possibly <code>null</code>). 212 */ 213 public static Range combine(Range range1, Range range2) { 214 if (range1 == null) { 215 return range2; 216 } 217 else { 218 if (range2 == null) { 219 return range1; 220 } 221 else { 222 double l = Math.min(range1.getLowerBound(), 223 range2.getLowerBound()); 224 double u = Math.max(range1.getUpperBound(), 225 range2.getUpperBound()); 226 return new Range(l, u); 227 } 228 } 229 } 230 231 /** 232 * Returns a range that includes all the values in the specified 233 * <code>range</code> AND the specified <code>value</code>. 234 * 235 * @param range the range (<code>null</code> permitted). 236 * @param value the value that must be included. 237 * 238 * @return A range. 239 * 240 * @since 1.0.1 241 */ 242 public static Range expandToInclude(Range range, double value) { 243 if (range == null) { 244 return new Range(value, value); 245 } 246 if (value < range.getLowerBound()) { 247 return new Range(value, range.getUpperBound()); 248 } 249 else if (value > range.getUpperBound()) { 250 return new Range(range.getLowerBound(), value); 251 } 252 else { 253 return range; 254 } 255 } 256 257 /** 258 * Creates a new range by adding margins to an existing range. 259 * 260 * @param range the range (<code>null</code> not permitted). 261 * @param lowerMargin the lower margin (expressed as a percentage of the 262 * range length). 263 * @param upperMargin the upper margin (expressed as a percentage of the 264 * range length). 265 * 266 * @return The expanded range. 267 */ 268 public static Range expand(Range range, 269 double lowerMargin, double upperMargin) { 270 if (range == null) { 271 throw new IllegalArgumentException("Null 'range' argument."); 272 } 273 double length = range.getLength(); 274 double lower = range.getLowerBound() - length * lowerMargin; 275 double upper = range.getUpperBound() + length * upperMargin; 276 if (lower > upper) { 277 lower = lower / 2.0 + upper / 2.0; 278 upper = lower; 279 } 280 return new Range(lower, upper); 281 } 282 283 /** 284 * Shifts the range by the specified amount. 285 * 286 * @param base the base range (<code>null</code> not permitted). 287 * @param delta the shift amount. 288 * 289 * @return A new range. 290 */ 291 public static Range shift(Range base, double delta) { 292 return shift(base, delta, false); 293 } 294 295 /** 296 * Shifts the range by the specified amount. 297 * 298 * @param base the base range (<code>null</code> not permitted). 299 * @param delta the shift amount. 300 * @param allowZeroCrossing a flag that determines whether or not the 301 * bounds of the range are allowed to cross 302 * zero after adjustment. 303 * 304 * @return A new range. 305 */ 306 public static Range shift(Range base, double delta, 307 boolean allowZeroCrossing) { 308 if (base == null) { 309 throw new IllegalArgumentException("Null 'base' argument."); 310 } 311 if (allowZeroCrossing) { 312 return new Range(base.getLowerBound() + delta, 313 base.getUpperBound() + delta); 314 } 315 else { 316 return new Range(shiftWithNoZeroCrossing(base.getLowerBound(), 317 delta), shiftWithNoZeroCrossing(base.getUpperBound(), 318 delta)); 319 } 320 } 321 322 /** 323 * Returns the given <code>value</code> adjusted by <code>delta</code> but 324 * with a check to prevent the result from crossing <code>0.0</code>. 325 * 326 * @param value the value. 327 * @param delta the adjustment. 328 * 329 * @return The adjusted value. 330 */ 331 private static double shiftWithNoZeroCrossing(double value, double delta) { 332 if (value > 0.0) { 333 return Math.max(value + delta, 0.0); 334 } 335 else if (value < 0.0) { 336 return Math.min(value + delta, 0.0); 337 } 338 else { 339 return value + delta; 340 } 341 } 342 343 /** 344 * Scales the range by the specified factor. 345 * 346 * @param base the base range (<code>null</code> not permitted). 347 * @param factor the scaling factor (must be non-negative). 348 * 349 * @return A new range. 350 * 351 * @since 1.0.9 352 */ 353 public static Range scale(Range base, double factor) { 354 if (base == null) { 355 throw new IllegalArgumentException("Null 'base' argument."); 356 } 357 if (factor < 0) { 358 throw new IllegalArgumentException("Negative 'factor' argument."); 359 } 360 return new Range(base.getLowerBound() * factor, 361 base.getUpperBound() * factor); 362 } 363 364 /** 365 * Tests this object for equality with an arbitrary object. 366 * 367 * @param obj the object to test against (<code>null</code> permitted). 368 * 369 * @return A boolean. 370 */ 371 public boolean equals(Object obj) { 372 if (!(obj instanceof Range)) { 373 return false; 374 } 375 Range range = (Range) obj; 376 if (!(this.lower == range.lower)) { 377 return false; 378 } 379 if (!(this.upper == range.upper)) { 380 return false; 381 } 382 return true; 383 } 384 385 /** 386 * Returns a hash code. 387 * 388 * @return A hash code. 389 */ 390 public int hashCode() { 391 int result; 392 long temp; 393 temp = Double.doubleToLongBits(this.lower); 394 result = (int) (temp ^ (temp >>> 32)); 395 temp = Double.doubleToLongBits(this.upper); 396 result = 29 * result + (int) (temp ^ (temp >>> 32)); 397 return result; 398 } 399 400 /** 401 * Returns a string representation of this Range. 402 * 403 * @return A String "Range[lower,upper]" where lower=lower range and 404 * upper=upper range. 405 */ 406 public String toString() { 407 return ("Range[" + this.lower + "," + this.upper + "]"); 408 } 409 410 }