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