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 }