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