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 * LookupPaintScale.java
029 * ---------------------
030 * (C) Copyright 2006, 2007, by Object Refinery Limited.
031 *
032 * Original Author: David Gilbert (for Object Refinery Limited);
033 * Contributor(s): -;
034 *
035 * $Id: LookupPaintScale.java,v 1.1.2.4 2007/06/14 13:20:19 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 05-Jul-2006 : Version 1 (DG);
040 * 31-Jan-2007 : Fixed serialization support (DG);
041 * 09-Mar-2007 : Fixed cloning (DG);
042 * 14-Jun-2007 : Use double primitive in PaintItem (DG);
043 *
044 */
045
046 package org.jfree.chart.renderer;
047
048 import java.awt.Color;
049 import java.awt.Paint;
050 import java.io.IOException;
051 import java.io.ObjectInputStream;
052 import java.io.ObjectOutputStream;
053 import java.io.Serializable;
054 import java.util.Collections;
055 import java.util.List;
056
057 import org.jfree.io.SerialUtilities;
058 import org.jfree.util.PaintUtilities;
059 import org.jfree.util.PublicCloneable;
060
061 /**
062 * A paint scale that uses a lookup table to associate paint instances
063 * with data value ranges.
064 *
065 * @since 1.0.4
066 */
067 public class LookupPaintScale
068 implements PaintScale, PublicCloneable, Serializable {
069
070 /**
071 * Stores the paint for a value.
072 */
073 class PaintItem implements Comparable, Serializable {
074
075 /** The value. */
076 double value;
077
078 /** The paint. */
079 transient Paint paint;
080
081 /**
082 * Creates a new instance.
083 *
084 * @param value the value.
085 * @param paint the paint.
086 */
087 public PaintItem(double value, Paint paint) {
088 this.value = value;
089 this.paint = paint;
090 }
091
092 /* (non-Javadoc)
093 * @see java.lang.Comparable#compareTo(java.lang.Object)
094 */
095 public int compareTo(Object obj) {
096 PaintItem that = (PaintItem) obj;
097 double d1 = this.value;
098 double d2 = that.value;
099 if (d1 > d2) {
100 return 1;
101 }
102 if (d1 < d2) {
103 return -1;
104 }
105 return 0;
106 }
107
108 /**
109 * Tests this item for equality with an arbitrary object.
110 *
111 * @param obj the object (<code>null</code> permitted).
112 *
113 * @return A boolean.
114 */
115 public boolean equals(Object obj) {
116 if (obj == this) {
117 return true;
118 }
119 if (!(obj instanceof PaintItem)) {
120 return false;
121 }
122 PaintItem that = (PaintItem) obj;
123 if (this.value != that.value) {
124 return false;
125 }
126 if (!PaintUtilities.equal(this.paint, that.paint)) {
127 return false;
128 }
129 return true;
130 }
131
132 /**
133 * Provides serialization support.
134 *
135 * @param stream the output stream.
136 *
137 * @throws IOException if there is an I/O error.
138 */
139 private void writeObject(ObjectOutputStream stream) throws IOException {
140 stream.defaultWriteObject();
141 SerialUtilities.writePaint(this.paint, stream);
142 }
143
144 /**
145 * Provides serialization support.
146 *
147 * @param stream the input stream.
148 *
149 * @throws IOException if there is an I/O error.
150 * @throws ClassNotFoundException if there is a classpath problem.
151 */
152 private void readObject(ObjectInputStream stream)
153 throws IOException, ClassNotFoundException {
154 stream.defaultReadObject();
155 this.paint = SerialUtilities.readPaint(stream);
156 }
157
158 }
159
160 /** The lower bound. */
161 private double lowerBound;
162
163 /** The upper bound. */
164 private double upperBound;
165
166 /** The default paint. */
167 private transient Paint defaultPaint;
168
169 /** The lookup table. */
170 private List lookupTable;
171
172 /**
173 * Creates a new paint scale.
174 */
175 public LookupPaintScale() {
176 this(0.0, 1.0, Color.lightGray);
177 }
178
179 /**
180 * Creates a new paint scale with the specified default paint.
181 *
182 * @param lowerBound the lower bound.
183 * @param upperBound the upper bound.
184 * @param defaultPaint the default paint (<code>null</code> not
185 * permitted).
186 */
187 public LookupPaintScale(double lowerBound, double upperBound,
188 Paint defaultPaint) {
189 if (lowerBound >= upperBound) {
190 throw new IllegalArgumentException(
191 "Requires lowerBound < upperBound.");
192 }
193 if (defaultPaint == null) {
194 throw new IllegalArgumentException("Null 'paint' argument.");
195 }
196 this.lowerBound = lowerBound;
197 this.upperBound = upperBound;
198 this.defaultPaint = defaultPaint;
199 this.lookupTable = new java.util.ArrayList();
200 }
201
202 /**
203 * Returns the default paint (never <code>null</code>).
204 *
205 * @return The default paint.
206 */
207 public Paint getDefaultPaint() {
208 return this.defaultPaint;
209 }
210
211 /**
212 * Returns the lower bound.
213 *
214 * @return The lower bound.
215 *
216 * @see #getUpperBound()
217 */
218 public double getLowerBound() {
219 return this.lowerBound;
220 }
221
222 /**
223 * Returns the upper bound.
224 *
225 * @return The upper bound.
226 *
227 * @see #getLowerBound()
228 */
229 public double getUpperBound() {
230 return this.upperBound;
231 }
232
233 /**
234 * Adds an entry to the lookup table. Any values from <code>n</code> up
235 * to but not including the next value in the table take on the specified
236 * <code>paint</code>.
237 *
238 * @param value the data value (<code>null</code> not permitted).
239 * @param paint the paint.
240 *
241 * @deprecated Use {@link #add(double, Paint)}.
242 */
243 public void add(Number value, Paint paint) {
244 add(value.doubleValue(), paint);
245 }
246
247 /**
248 * Adds an entry to the lookup table. Any values from <code>n</code> up
249 * to but not including the next value in the table take on the specified
250 * <code>paint</code>.
251 *
252 * @param value the data value.
253 * @param paint the paint.
254 *
255 * @since 1.0.6
256 */
257 public void add(double value, Paint paint) {
258 PaintItem item = new PaintItem(value, paint);
259 int index = Collections.binarySearch(this.lookupTable, item);
260 if (index >= 0) {
261 this.lookupTable.set(index, item);
262 }
263 else {
264 this.lookupTable.add(-(index + 1), item);
265 }
266 }
267
268 /**
269 * Returns the paint associated with the specified value.
270 *
271 * @param value the value.
272 *
273 * @return The paint.
274 *
275 * @see #getDefaultPaint()
276 */
277 public Paint getPaint(double value) {
278
279 // handle value outside bounds...
280 if (value < this.lowerBound) {
281 return this.defaultPaint;
282 }
283 if (value > this.upperBound) {
284 return this.defaultPaint;
285 }
286
287 int count = this.lookupTable.size();
288 if (count == 0) {
289 return this.defaultPaint;
290 }
291
292 // handle special case where value is less that item zero
293 PaintItem item = (PaintItem) this.lookupTable.get(0);
294 if (value < item.value) {
295 return this.defaultPaint;
296 }
297
298 // for value in bounds, do the lookup...
299 int low = 0;
300 int high = this.lookupTable.size() - 1;
301 while (high - low > 1) {
302 int current = (low + high) / 2;
303 item = (PaintItem) this.lookupTable.get(current);
304 if (value >= item.value) {
305 low = current;
306 }
307 else {
308 high = current;
309 }
310 }
311 if (high > low) {
312 item = (PaintItem) this.lookupTable.get(high);
313 if (value < item.value) {
314 item = (PaintItem) this.lookupTable.get(low);
315 }
316 }
317 return (item != null ? item.paint : this.defaultPaint);
318 }
319
320
321 /**
322 * Tests this instance for equality with an arbitrary object.
323 *
324 * @param obj the object (<code>null</code> permitted).
325 *
326 * @return A boolean.
327 */
328 public boolean equals(Object obj) {
329 if (obj == this) {
330 return true;
331 }
332 if (!(obj instanceof LookupPaintScale)) {
333 return false;
334 }
335 LookupPaintScale that = (LookupPaintScale) obj;
336 if (this.lowerBound != that.lowerBound) {
337 return false;
338 }
339 if (this.upperBound != that.upperBound) {
340 return false;
341 }
342 if (!PaintUtilities.equal(this.defaultPaint, that.defaultPaint)) {
343 return false;
344 }
345 if (!this.lookupTable.equals(that.lookupTable)) {
346 return false;
347 }
348 return true;
349 }
350
351 /**
352 * Returns a clone of the instance.
353 *
354 * @return A clone.
355 *
356 * @throws CloneNotSupportedException if there is a problem cloning the
357 * instance.
358 */
359 public Object clone() throws CloneNotSupportedException {
360 LookupPaintScale clone = (LookupPaintScale) super.clone();
361 clone.lookupTable = new java.util.ArrayList(this.lookupTable);
362 return clone;
363 }
364
365 /**
366 * Provides serialization support.
367 *
368 * @param stream the output stream.
369 *
370 * @throws IOException if there is an I/O error.
371 */
372 private void writeObject(ObjectOutputStream stream) throws IOException {
373 stream.defaultWriteObject();
374 SerialUtilities.writePaint(this.defaultPaint, stream);
375 }
376
377 /**
378 * Provides serialization support.
379 *
380 * @param stream the input stream.
381 *
382 * @throws IOException if there is an I/O error.
383 * @throws ClassNotFoundException if there is a classpath problem.
384 */
385 private void readObject(ObjectInputStream stream)
386 throws IOException, ClassNotFoundException {
387 stream.defaultReadObject();
388 this.defaultPaint = SerialUtilities.readPaint(stream);
389 }
390
391 }