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