001 /* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2007, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ----------
028 * Range.java
029 * ----------
030 * (C) Copyright 2002-2007, 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 *
037 * Changes (from 23-Jun-2001)
038 * --------------------------
039 * 22-Apr-2002 : Version 1, loosely based by code by Bill Kelemen (DG);
040 * 30-Apr-2002 : Added getLength() and getCentralValue() methods. Changed
041 * argument check in constructor (DG);
042 * 13-Jun-2002 : Added contains(double) method (DG);
043 * 22-Aug-2002 : Added fix to combine method where both ranges are null, thanks
044 * to Chuanhao Chiu for reporting and fixing this (DG);
045 * 07-Oct-2002 : Fixed errors reported by Checkstyle (DG);
046 * 26-Mar-2003 : Implemented Serializable (DG);
047 * 14-Aug-2003 : Added equals() method (DG);
048 * 27-Aug-2003 : Added toString() method (BK);
049 * 11-Sep-2003 : Added Clone Support (NB);
050 * 23-Sep-2003 : Fixed Checkstyle issues (DG);
051 * 25-Sep-2003 : Oops, Range immutable, clone not necessary (NB);
052 * 05-May-2004 : Added constrain() and intersects() methods (DG);
053 * 18-May-2004 : Added expand() method (DG);
054 * ------------- JFreeChart 1.0.x ---------------------------------------------
055 * 11-Jan-2006 : Added new method expandToInclude(Range, double) (DG);
056 *
057 */
058
059 package org.jfree.data;
060
061 import java.io.Serializable;
062
063 /**
064 * Represents an immutable range of values.
065 */
066 public strictfp class Range implements Serializable {
067
068 /** For serialization. */
069 private static final long serialVersionUID = -906333695431863380L;
070
071 /** The lower bound of the range. */
072 private double lower;
073
074 /** The upper bound of the range. */
075 private double upper;
076
077 /**
078 * Creates a new range.
079 *
080 * @param lower the lower bound (must be <= upper bound).
081 * @param upper the upper bound (must be >= lower bound).
082 */
083 public Range(double lower, double upper) {
084 if (lower > upper) {
085 String msg = "Range(double, double): require lower (" + lower
086 + ") <= upper (" + upper + ").";
087 throw new IllegalArgumentException(msg);
088 }
089 this.lower = lower;
090 this.upper = upper;
091 }
092
093 /**
094 * Returns the lower bound for the range.
095 *
096 * @return The lower bound.
097 */
098 public double getLowerBound() {
099 return this.lower;
100 }
101
102 /**
103 * Returns the upper bound for the range.
104 *
105 * @return The upper bound.
106 */
107 public double getUpperBound() {
108 return this.upper;
109 }
110
111 /**
112 * Returns the length of the range.
113 *
114 * @return The length.
115 */
116 public double getLength() {
117 return this.upper - this.lower;
118 }
119
120 /**
121 * Returns the central value for the range.
122 *
123 * @return The central value.
124 */
125 public double getCentralValue() {
126 return this.lower / 2.0 + this.upper / 2.0;
127 }
128
129 /**
130 * Returns <code>true</code> if the range contains the specified value and
131 * <code>false</code> otherwise.
132 *
133 * @param value the value to lookup.
134 *
135 * @return <code>true</code> if the range contains the specified value.
136 */
137 public boolean contains(double value) {
138 return (value >= this.lower && value <= this.upper);
139 }
140
141 /**
142 * Returns <code>true</code> if the range intersects with the specified
143 * range, and <code>false</code> otherwise.
144 *
145 * @param b0 the lower bound (should be <= b1).
146 * @param b1 the upper bound (should be >= b0).
147 *
148 * @return A boolean.
149 */
150 public boolean intersects(double b0, double b1) {
151 if (b0 <= this.lower) {
152 return (b1 > this.lower);
153 }
154 else {
155 return (b0 < this.upper && b1 >= b0);
156 }
157 }
158
159 /**
160 * Returns the value within the range that is closest to the specified
161 * value.
162 *
163 * @param value the value.
164 *
165 * @return The constrained value.
166 */
167 public double constrain(double value) {
168 double result = value;
169 if (!contains(value)) {
170 if (value > this.upper) {
171 result = this.upper;
172 }
173 else if (value < this.lower) {
174 result = this.lower;
175 }
176 }
177 return result;
178 }
179
180 /**
181 * Creates a new range by combining two existing ranges.
182 * <P>
183 * Note that:
184 * <ul>
185 * <li>either range can be <code>null</code>, in which case the other
186 * range is returned;</li>
187 * <li>if both ranges are <code>null</code> the return value is
188 * <code>null</code>.</li>
189 * </ul>
190 *
191 * @param range1 the first range (<code>null</code> permitted).
192 * @param range2 the second range (<code>null</code> permitted).
193 *
194 * @return A new range (possibly <code>null</code>).
195 */
196 public static Range combine(Range range1, Range range2) {
197 if (range1 == null) {
198 return range2;
199 }
200 else {
201 if (range2 == null) {
202 return range1;
203 }
204 else {
205 double l = Math.min(range1.getLowerBound(),
206 range2.getLowerBound());
207 double u = Math.max(range1.getUpperBound(),
208 range2.getUpperBound());
209 return new Range(l, u);
210 }
211 }
212 }
213
214 /**
215 * Returns a range that includes all the values in the specified
216 * <code>range</code> AND the specified <code>value</code>.
217 *
218 * @param range the range (<code>null</code> permitted).
219 * @param value the value that must be included.
220 *
221 * @return A range.
222 *
223 * @since 1.0.1
224 */
225 public static Range expandToInclude(Range range, double value) {
226 if (range == null) {
227 return new Range(value, value);
228 }
229 if (value < range.getLowerBound()) {
230 return new Range(value, range.getUpperBound());
231 }
232 else if (value > range.getUpperBound()) {
233 return new Range(range.getLowerBound(), value);
234 }
235 else {
236 return range;
237 }
238 }
239
240 /**
241 * Creates a new range by adding margins to an existing range.
242 *
243 * @param range the range (<code>null</code> not permitted).
244 * @param lowerMargin the lower margin (expressed as a percentage of the
245 * range length).
246 * @param upperMargin the upper margin (expressed as a percentage of the
247 * range length).
248 *
249 * @return The expanded range.
250 */
251 public static Range expand(Range range,
252 double lowerMargin, double upperMargin) {
253 if (range == null) {
254 throw new IllegalArgumentException("Null 'range' argument.");
255 }
256 double length = range.getLength();
257 double lower = length * lowerMargin;
258 double upper = length * upperMargin;
259 return new Range(range.getLowerBound() - lower,
260 range.getUpperBound() + upper);
261 }
262
263 /**
264 * Shifts the range by the specified amount.
265 *
266 * @param base the base range.
267 * @param delta the shift amount.
268 *
269 * @return A new range.
270 */
271 public static Range shift(Range base, double delta) {
272 return shift(base, delta, false);
273 }
274
275 /**
276 * Shifts the range by the specified amount.
277 *
278 * @param base the base range.
279 * @param delta the shift amount.
280 * @param allowZeroCrossing a flag that determines whether or not the
281 * bounds of the range are allowed to cross
282 * zero after adjustment.
283 *
284 * @return A new range.
285 */
286 public static Range shift(Range base, double delta,
287 boolean allowZeroCrossing) {
288 if (allowZeroCrossing) {
289 return new Range(base.getLowerBound() + delta,
290 base.getUpperBound() + delta);
291 }
292 else {
293 return new Range(shiftWithNoZeroCrossing(base.getLowerBound(),
294 delta), shiftWithNoZeroCrossing(base.getUpperBound(),
295 delta));
296 }
297 }
298
299 /**
300 * Returns the given <code>value</code> adjusted by <code>delta</code> but
301 * with a check to prevent the result from crossing <code>0.0</code>.
302 *
303 * @param value the value.
304 * @param delta the adjustment.
305 *
306 * @return The adjusted value.
307 */
308 private static double shiftWithNoZeroCrossing(double value, double delta) {
309 if (value > 0.0) {
310 return Math.max(value + delta, 0.0);
311 }
312 else if (value < 0.0) {
313 return Math.min(value + delta, 0.0);
314 }
315 else {
316 return value + delta;
317 }
318 }
319
320 /**
321 * Tests this object for equality with an arbitrary object.
322 *
323 * @param obj the object to test against (<code>null</code> permitted).
324 *
325 * @return A boolean.
326 */
327 public boolean equals(Object obj) {
328 if (!(obj instanceof Range)) {
329 return false;
330 }
331 Range range = (Range) obj;
332 if (!(this.lower == range.lower)) {
333 return false;
334 }
335 if (!(this.upper == range.upper)) {
336 return false;
337 }
338 return true;
339 }
340
341 /**
342 * Returns a hash code.
343 *
344 * @return A hash code.
345 */
346 public int hashCode() {
347 int result;
348 long temp;
349 temp = Double.doubleToLongBits(this.lower);
350 result = (int) (temp ^ (temp >>> 32));
351 temp = Double.doubleToLongBits(this.upper);
352 result = 29 * result + (int) (temp ^ (temp >>> 32));
353 return result;
354 }
355
356 /**
357 * Returns a string representation of this Range.
358 *
359 * @return A String "Range[lower,upper]" where lower=lower range and
360 * upper=upper range.
361 */
362 public String toString() {
363 return ("Range[" + this.lower + "," + this.upper + "]");
364 }
365
366 }