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