001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ---------- 028 * Range.java 029 * ---------- 030 * (C) Copyright 2002-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Chuanhao Chiu; 034 * Bill Kelemen; 035 * Nicolas Brodu; 036 * Sergei Ivanov; 037 * Tracy Hiltbrand (equals complies with EqualsVerifier); 038 * 039 */ 040 041package org.jfree.data; 042 043import java.io.Serializable; 044import org.jfree.chart.util.Args; 045 046/** 047 * Represents an immutable range of values. 048 */ 049public strictfp class Range implements Serializable { 050 051 /** For serialization. */ 052 private static final long serialVersionUID = -906333695431863380L; 053 054 /** The lower bound of the range. */ 055 private double lower; 056 057 /** The upper bound of the range. */ 058 private double upper; 059 060 /** 061 * Creates a new range. 062 * 063 * @param lower the lower bound (must be <= upper bound). 064 * @param upper the upper bound (must be >= lower bound). 065 */ 066 public Range(double lower, double upper) { 067 if (lower > upper) { 068 String msg = "Range(double, double): require lower (" + lower 069 + ") <= upper (" + upper + ")."; 070 throw new IllegalArgumentException(msg); 071 } 072 this.lower = lower; 073 this.upper = upper; 074 } 075 076 /** 077 * Returns the lower bound for the range. 078 * 079 * @return The lower bound. 080 */ 081 public double getLowerBound() { 082 return this.lower; 083 } 084 085 /** 086 * Returns the upper bound for the range. 087 * 088 * @return The upper bound. 089 */ 090 public double getUpperBound() { 091 return this.upper; 092 } 093 094 /** 095 * Returns the length of the range. 096 * 097 * @return The length. 098 */ 099 public double getLength() { 100 return this.upper - this.lower; 101 } 102 103 /** 104 * Returns the central value for the range. 105 * 106 * @return The central value. 107 */ 108 public double getCentralValue() { 109 return this.lower / 2.0 + this.upper / 2.0; 110 } 111 112 /** 113 * Returns {@code true} if the range contains the specified value and 114 * {@code false} otherwise. 115 * 116 * @param value the value to lookup. 117 * 118 * @return {@code true} if the range contains the specified value. 119 */ 120 public boolean contains(double value) { 121 return (value >= this.lower && value <= this.upper); 122 } 123 124 /** 125 * Returns {@code true} if the range intersects with the specified 126 * range, and {@code false} otherwise. 127 * 128 * @param b0 the lower bound (should be <= b1). 129 * @param b1 the upper bound (should be >= b0). 130 * 131 * @return A boolean. 132 */ 133 public boolean intersects(double b0, double b1) { 134 if (b0 <= this.lower) { 135 return (b1 > this.lower); 136 } 137 else { 138 return (b0 < this.upper && b1 >= b0); 139 } 140 } 141 142 /** 143 * Returns {@code true} if the range intersects with the specified 144 * range, and {@code false} otherwise. 145 * 146 * @param range another range ({@code null} not permitted). 147 * 148 * @return A boolean. 149 */ 150 public boolean intersects(Range range) { 151 return intersects(range.getLowerBound(), range.getUpperBound()); 152 } 153 154 /** 155 * Returns the value within the range that is closest to the specified 156 * value. 157 * 158 * @param value the value. 159 * 160 * @return The constrained value. 161 */ 162 public double constrain(double value) { 163 if (contains(value)) { 164 return value; 165 } 166 if (value > this.upper) { 167 return this.upper; 168 } 169 if (value < this.lower) { 170 return this.lower; 171 } 172 return value; // covers Double.NaN 173 } 174 175 /** 176 * Creates a new range by combining two existing ranges. 177 * <P> 178 * Note that: 179 * <ul> 180 * <li>either range can be {@code null}, in which case the other 181 * range is returned;</li> 182 * <li>if both ranges are {@code null} the return value is 183 * {@code null}.</li> 184 * </ul> 185 * 186 * @param range1 the first range ({@code null} permitted). 187 * @param range2 the second range ({@code null} permitted). 188 * 189 * @return A new range (possibly {@code null}). 190 */ 191 public static Range combine(Range range1, Range range2) { 192 if (range1 == null) { 193 return range2; 194 } 195 if (range2 == null) { 196 return range1; 197 } 198 double l = Math.min(range1.getLowerBound(), range2.getLowerBound()); 199 double u = Math.max(range1.getUpperBound(), range2.getUpperBound()); 200 return new Range(l, u); 201 } 202 203 /** 204 * Returns a new range that spans both {@code range1} and 205 * {@code range2}. This method has a special handling to ignore 206 * Double.NaN values. 207 * 208 * @param range1 the first range ({@code null} permitted). 209 * @param range2 the second range ({@code null} permitted). 210 * 211 * @return A new range (possibly {@code null}). 212 */ 213 public static Range combineIgnoringNaN(Range range1, Range range2) { 214 if (range1 == null) { 215 if (range2 != null && range2.isNaNRange()) { 216 return null; 217 } 218 return range2; 219 } 220 if (range2 == null) { 221 if (range1.isNaNRange()) { 222 return null; 223 } 224 return range1; 225 } 226 double l = min(range1.getLowerBound(), range2.getLowerBound()); 227 double u = max(range1.getUpperBound(), range2.getUpperBound()); 228 if (Double.isNaN(l) && Double.isNaN(u)) { 229 return null; 230 } 231 return new Range(l, u); 232 } 233 234 /** 235 * Returns the minimum value. If either value is NaN, the other value is 236 * returned. If both are NaN, NaN is returned. 237 * 238 * @param d1 value 1. 239 * @param d2 value 2. 240 * 241 * @return The minimum of the two values. 242 */ 243 private static double min(double d1, double d2) { 244 if (Double.isNaN(d1)) { 245 return d2; 246 } 247 if (Double.isNaN(d2)) { 248 return d1; 249 } 250 return Math.min(d1, d2); 251 } 252 253 private static double max(double d1, double d2) { 254 if (Double.isNaN(d1)) { 255 return d2; 256 } 257 if (Double.isNaN(d2)) { 258 return d1; 259 } 260 return Math.max(d1, d2); 261 } 262 263 /** 264 * Returns a range that includes all the values in the specified 265 * {@code range} AND the specified {@code value}. 266 * 267 * @param range the range ({@code null} permitted). 268 * @param value the value that must be included. 269 * 270 * @return A range. 271 */ 272 public static Range expandToInclude(Range range, double value) { 273 if (range == null) { 274 return new Range(value, value); 275 } 276 if (value < range.getLowerBound()) { 277 return new Range(value, range.getUpperBound()); 278 } 279 else if (value > range.getUpperBound()) { 280 return new Range(range.getLowerBound(), value); 281 } 282 else { 283 return range; 284 } 285 } 286 287 /** 288 * Creates a new range by adding margins to an existing range. 289 * 290 * @param range the range ({@code null} not permitted). 291 * @param lowerMargin the lower margin (expressed as a percentage of the 292 * range length). 293 * @param upperMargin the upper margin (expressed as a percentage of the 294 * range length). 295 * 296 * @return The expanded range. 297 */ 298 public static Range expand(Range range, 299 double lowerMargin, double upperMargin) { 300 Args.nullNotPermitted(range, "range"); 301 double length = range.getLength(); 302 double lower = range.getLowerBound() - length * lowerMargin; 303 double upper = range.getUpperBound() + length * upperMargin; 304 if (lower > upper) { 305 lower = lower / 2.0 + upper / 2.0; 306 upper = lower; 307 } 308 return new Range(lower, upper); 309 } 310 311 /** 312 * Shifts the range by the specified amount. 313 * 314 * @param base the base range ({@code null} not permitted). 315 * @param delta the shift amount. 316 * 317 * @return A new range. 318 */ 319 public static Range shift(Range base, double delta) { 320 return shift(base, delta, false); 321 } 322 323 /** 324 * Shifts the range by the specified amount. 325 * 326 * @param base the base range ({@code null} not permitted). 327 * @param delta the shift amount. 328 * @param allowZeroCrossing a flag that determines whether or not the 329 * bounds of the range are allowed to cross 330 * zero after adjustment. 331 * 332 * @return A new range. 333 */ 334 public static Range shift(Range base, double delta, 335 boolean allowZeroCrossing) { 336 Args.nullNotPermitted(base, "base"); 337 if (allowZeroCrossing) { 338 return new Range(base.getLowerBound() + delta, 339 base.getUpperBound() + delta); 340 } 341 else { 342 return new Range(shiftWithNoZeroCrossing(base.getLowerBound(), 343 delta), shiftWithNoZeroCrossing(base.getUpperBound(), 344 delta)); 345 } 346 } 347 348 /** 349 * Returns the given {@code value} adjusted by {@code delta} but 350 * with a check to prevent the result from crossing {@code 0.0}. 351 * 352 * @param value the value. 353 * @param delta the adjustment. 354 * 355 * @return The adjusted value. 356 */ 357 private static double shiftWithNoZeroCrossing(double value, double delta) { 358 if (value > 0.0) { 359 return Math.max(value + delta, 0.0); 360 } 361 else if (value < 0.0) { 362 return Math.min(value + delta, 0.0); 363 } 364 else { 365 return value + delta; 366 } 367 } 368 369 /** 370 * Scales the range by the specified factor. 371 * 372 * @param base the base range ({@code null} not permitted). 373 * @param factor the scaling factor (must be non-negative). 374 * 375 * @return A new range. 376 */ 377 public static Range scale(Range base, double factor) { 378 Args.nullNotPermitted(base, "base"); 379 if (factor < 0) { 380 throw new IllegalArgumentException("Negative 'factor' argument."); 381 } 382 return new Range(base.getLowerBound() * factor, 383 base.getUpperBound() * factor); 384 } 385 386 /** 387 * Tests this object for equality with an arbitrary object. 388 * 389 * @param obj the object to test against ({@code null} permitted). 390 * 391 * @return A boolean. 392 */ 393 @Override 394 public boolean equals(Object obj) { 395 if (!(obj instanceof Range)) { 396 return false; 397 } 398 Range range = (Range) obj; 399 if (Double.doubleToLongBits(this.lower) != 400 Double.doubleToLongBits(range.lower)) { 401 return false; 402 } 403 if (Double.doubleToLongBits(this.upper) != 404 Double.doubleToLongBits(range.upper)) { 405 return false; 406 } 407 return true; 408 } 409 410 /** 411 * Returns {@code true} if both the lower and upper bounds are 412 * {@code Double.NaN}, and {@code false} otherwise. 413 * 414 * @return A boolean. 415 */ 416 public boolean isNaNRange() { 417 return Double.isNaN(this.lower) && Double.isNaN(this.upper); 418 } 419 420 /** 421 * Returns a hash code. 422 * 423 * @return A hash code. 424 */ 425 @Override 426 public int hashCode() { 427 int result; 428 long temp; 429 temp = Double.doubleToLongBits(this.lower); 430 result = (int) (temp ^ (temp >>> 32)); 431 temp = Double.doubleToLongBits(this.upper); 432 result = 29 * result + (int) (temp ^ (temp >>> 32)); 433 return result; 434 } 435 436 /** 437 * Returns a string representation of this Range. 438 * 439 * @return A String "Range[lower,upper]" where lower=lower range and 440 * upper=upper range. 441 */ 442 @Override 443 public String toString() { 444 return ("Range[" + this.lower + "," + this.upper + "]"); 445 } 446 447}