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