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