001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2020, 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-2016, 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.Args; 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} if the range contains the specified value and 137 * {@code false} otherwise. 138 * 139 * @param value the value to lookup. 140 * 141 * @return {@code true} 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} if the range intersects with the specified 149 * range, and {@code false} 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} if the range intersects with the specified 167 * range, and {@code false} otherwise. 168 * 169 * @param range another range ({@code null} not permitted). 170 * 171 * @return A boolean. 172 */ 173 public boolean intersects(Range range) { 174 return intersects(range.getLowerBound(), range.getUpperBound()); 175 } 176 177 /** 178 * Returns the value within the range that is closest to the specified 179 * value. 180 * 181 * @param value the value. 182 * 183 * @return The constrained value. 184 */ 185 public double constrain(double value) { 186 if (contains(value)) { 187 return value; 188 } 189 if (value > this.upper) { 190 return this.upper; 191 } 192 if (value < this.lower) { 193 return this.lower; 194 } 195 return value; // covers Double.NaN 196 } 197 198 /** 199 * Creates a new range by combining two existing ranges. 200 * <P> 201 * Note that: 202 * <ul> 203 * <li>either range can be {@code null}, in which case the other 204 * range is returned;</li> 205 * <li>if both ranges are {@code null} the return value is 206 * {@code null}.</li> 207 * </ul> 208 * 209 * @param range1 the first range ({@code null} permitted). 210 * @param range2 the second range ({@code null} permitted). 211 * 212 * @return A new range (possibly {@code null}). 213 */ 214 public static Range combine(Range range1, Range range2) { 215 if (range1 == null) { 216 return range2; 217 } 218 if (range2 == null) { 219 return range1; 220 } 221 double l = Math.min(range1.getLowerBound(), range2.getLowerBound()); 222 double u = Math.max(range1.getUpperBound(), range2.getUpperBound()); 223 return new Range(l, u); 224 } 225 226 /** 227 * Returns a new range that spans both {@code range1} and 228 * {@code range2}. This method has a special handling to ignore 229 * Double.NaN values. 230 * 231 * @param range1 the first range ({@code null} permitted). 232 * @param range2 the second range ({@code null} permitted). 233 * 234 * @return A new range (possibly {@code null}). 235 */ 236 public static Range combineIgnoringNaN(Range range1, Range range2) { 237 if (range1 == null) { 238 if (range2 != null && range2.isNaNRange()) { 239 return null; 240 } 241 return range2; 242 } 243 if (range2 == null) { 244 if (range1.isNaNRange()) { 245 return null; 246 } 247 return range1; 248 } 249 double l = min(range1.getLowerBound(), range2.getLowerBound()); 250 double u = max(range1.getUpperBound(), range2.getUpperBound()); 251 if (Double.isNaN(l) && Double.isNaN(u)) { 252 return null; 253 } 254 return new Range(l, u); 255 } 256 257 /** 258 * Returns the minimum value. If either value is NaN, the other value is 259 * returned. If both are NaN, NaN is returned. 260 * 261 * @param d1 value 1. 262 * @param d2 value 2. 263 * 264 * @return The minimum of the two values. 265 */ 266 private static double min(double d1, double d2) { 267 if (Double.isNaN(d1)) { 268 return d2; 269 } 270 if (Double.isNaN(d2)) { 271 return d1; 272 } 273 return Math.min(d1, d2); 274 } 275 276 private static double max(double d1, double d2) { 277 if (Double.isNaN(d1)) { 278 return d2; 279 } 280 if (Double.isNaN(d2)) { 281 return d1; 282 } 283 return Math.max(d1, d2); 284 } 285 286 /** 287 * Returns a range that includes all the values in the specified 288 * {@code range} AND the specified {@code value}. 289 * 290 * @param range the range ({@code null} permitted). 291 * @param value the value that must be included. 292 * 293 * @return A range. 294 */ 295 public static Range expandToInclude(Range range, double value) { 296 if (range == null) { 297 return new Range(value, value); 298 } 299 if (value < range.getLowerBound()) { 300 return new Range(value, range.getUpperBound()); 301 } 302 else if (value > range.getUpperBound()) { 303 return new Range(range.getLowerBound(), value); 304 } 305 else { 306 return range; 307 } 308 } 309 310 /** 311 * Creates a new range by adding margins to an existing range. 312 * 313 * @param range the range ({@code null} not permitted). 314 * @param lowerMargin the lower margin (expressed as a percentage of the 315 * range length). 316 * @param upperMargin the upper margin (expressed as a percentage of the 317 * range length). 318 * 319 * @return The expanded range. 320 */ 321 public static Range expand(Range range, 322 double lowerMargin, double upperMargin) { 323 Args.nullNotPermitted(range, "range"); 324 double length = range.getLength(); 325 double lower = range.getLowerBound() - length * lowerMargin; 326 double upper = range.getUpperBound() + length * upperMargin; 327 if (lower > upper) { 328 lower = lower / 2.0 + upper / 2.0; 329 upper = lower; 330 } 331 return new Range(lower, upper); 332 } 333 334 /** 335 * Shifts the range by the specified amount. 336 * 337 * @param base the base range ({@code null} not permitted). 338 * @param delta the shift amount. 339 * 340 * @return A new range. 341 */ 342 public static Range shift(Range base, double delta) { 343 return shift(base, delta, false); 344 } 345 346 /** 347 * Shifts the range by the specified amount. 348 * 349 * @param base the base range ({@code null} not permitted). 350 * @param delta the shift amount. 351 * @param allowZeroCrossing a flag that determines whether or not the 352 * bounds of the range are allowed to cross 353 * zero after adjustment. 354 * 355 * @return A new range. 356 */ 357 public static Range shift(Range base, double delta, 358 boolean allowZeroCrossing) { 359 Args.nullNotPermitted(base, "base"); 360 if (allowZeroCrossing) { 361 return new Range(base.getLowerBound() + delta, 362 base.getUpperBound() + delta); 363 } 364 else { 365 return new Range(shiftWithNoZeroCrossing(base.getLowerBound(), 366 delta), shiftWithNoZeroCrossing(base.getUpperBound(), 367 delta)); 368 } 369 } 370 371 /** 372 * Returns the given {@code value} adjusted by {@code delta} but 373 * with a check to prevent the result from crossing {@code 0.0}. 374 * 375 * @param value the value. 376 * @param delta the adjustment. 377 * 378 * @return The adjusted value. 379 */ 380 private static double shiftWithNoZeroCrossing(double value, double delta) { 381 if (value > 0.0) { 382 return Math.max(value + delta, 0.0); 383 } 384 else if (value < 0.0) { 385 return Math.min(value + delta, 0.0); 386 } 387 else { 388 return value + delta; 389 } 390 } 391 392 /** 393 * Scales the range by the specified factor. 394 * 395 * @param base the base range ({@code null} not permitted). 396 * @param factor the scaling factor (must be non-negative). 397 * 398 * @return A new range. 399 */ 400 public static Range scale(Range base, double factor) { 401 Args.nullNotPermitted(base, "base"); 402 if (factor < 0) { 403 throw new IllegalArgumentException("Negative 'factor' argument."); 404 } 405 return new Range(base.getLowerBound() * factor, 406 base.getUpperBound() * factor); 407 } 408 409 /** 410 * Tests this object for equality with an arbitrary object. 411 * 412 * @param obj the object to test against ({@code null} permitted). 413 * 414 * @return A boolean. 415 */ 416 @Override 417 public boolean equals(Object obj) { 418 if (!(obj instanceof Range)) { 419 return false; 420 } 421 Range range = (Range) obj; 422 if (!(this.lower == range.lower)) { 423 return false; 424 } 425 if (!(this.upper == range.upper)) { 426 return false; 427 } 428 return true; 429 } 430 431 /** 432 * Returns {@code true} if both the lower and upper bounds are 433 * {@code Double.NaN}, and {@code false} otherwise. 434 * 435 * @return A boolean. 436 */ 437 public boolean isNaNRange() { 438 return Double.isNaN(this.lower) && Double.isNaN(this.upper); 439 } 440 441 /** 442 * Returns a hash code. 443 * 444 * @return A hash code. 445 */ 446 @Override 447 public int hashCode() { 448 int result; 449 long temp; 450 temp = Double.doubleToLongBits(this.lower); 451 result = (int) (temp ^ (temp >>> 32)); 452 temp = Double.doubleToLongBits(this.upper); 453 result = 29 * result + (int) (temp ^ (temp >>> 32)); 454 return result; 455 } 456 457 /** 458 * Returns a string representation of this Range. 459 * 460 * @return A String "Range[lower,upper]" where lower=lower range and 461 * upper=upper range. 462 */ 463 @Override 464 public String toString() { 465 return ("Range[" + this.lower + "," + this.upper + "]"); 466 } 467 468}