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 &lt;= b1).
152     * @param b1  the upper bound (should be &gt;= 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}