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