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 * StandardDialRange.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 * 03-Nov-2006 : Version 1 (DG); 038 * 08-Mar-2007 : Fix in hashCode() (DG); 039 * 17-Oct-2007 : Removed increment attribute (DG); 040 * 24-Oct-2007 : Added scaleIndex (DG); 041 * 042 */ 043 044package org.jfree.chart.plot.dial; 045 046import java.awt.BasicStroke; 047import java.awt.Color; 048import java.awt.Graphics2D; 049import java.awt.Paint; 050import java.awt.geom.Arc2D; 051import java.awt.geom.Rectangle2D; 052import java.io.IOException; 053import java.io.ObjectInputStream; 054import java.io.ObjectOutputStream; 055import java.io.Serializable; 056 057import org.jfree.chart.HashUtilities; 058import org.jfree.io.SerialUtilities; 059import org.jfree.util.PaintUtilities; 060import org.jfree.util.PublicCloneable; 061 062/** 063 * A layer that draws a range highlight on a dial plot. 064 */ 065public class StandardDialRange extends AbstractDialLayer implements DialLayer, 066 Cloneable, PublicCloneable, Serializable { 067 068 /** For serialization. */ 069 static final long serialVersionUID = 345515648249364904L; 070 071 /** The scale index. */ 072 private int scaleIndex; 073 074 /** The minimum data value for the scale. */ 075 private double lowerBound; 076 077 /** The maximum data value for the scale. */ 078 private double upperBound; 079 080 /** 081 * The paint used to draw the range highlight. This field is transient 082 * because it requires special handling for serialization. 083 */ 084 private transient Paint paint; 085 086 /** 087 * The factor (in the range 0.0 to 1.0) that determines the inside limit 088 * of the range highlight. 089 */ 090 private double innerRadius; 091 092 /** 093 * The factor (in the range 0.0 to 1.0) that determines the outside limit 094 * of the range highlight. 095 */ 096 private double outerRadius; 097 098 /** 099 * Creates a new instance of <code>StandardDialRange</code>. 100 */ 101 public StandardDialRange() { 102 this(0.0, 100.0, Color.white); 103 } 104 105 /** 106 * Creates a new instance of <code>StandardDialRange</code>. 107 * 108 * @param lower the lower bound. 109 * @param upper the upper bound. 110 * @param paint the paint (<code>null</code> not permitted). 111 */ 112 public StandardDialRange(double lower, double upper, Paint paint) { 113 if (paint == null) { 114 throw new IllegalArgumentException("Null 'paint' argument."); 115 } 116 this.scaleIndex = 0; 117 this.lowerBound = lower; 118 this.upperBound = upper; 119 this.innerRadius = 0.48; 120 this.outerRadius = 0.52; 121 this.paint = paint; 122 } 123 124 /** 125 * Returns the scale index. 126 * 127 * @return The scale index. 128 * 129 * @see #setScaleIndex(int) 130 */ 131 public int getScaleIndex() { 132 return this.scaleIndex; 133 } 134 135 /** 136 * Sets the scale index and sends a {@link DialLayerChangeEvent} to all 137 * registered listeners. 138 * 139 * @param index the scale index. 140 * 141 * @see #getScaleIndex() 142 */ 143 public void setScaleIndex(int index) { 144 this.scaleIndex = index; 145 notifyListeners(new DialLayerChangeEvent(this)); 146 } 147 148 /** 149 * Returns the lower bound (a data value) of the dial range. 150 * 151 * @return The lower bound of the dial range. 152 * 153 * @see #setLowerBound(double) 154 */ 155 public double getLowerBound() { 156 return this.lowerBound; 157 } 158 159 /** 160 * Sets the lower bound of the dial range and sends a 161 * {@link DialLayerChangeEvent} to all registered listeners. 162 * 163 * @param bound the lower bound. 164 * 165 * @see #getLowerBound() 166 */ 167 public void setLowerBound(double bound) { 168 if (bound >= this.upperBound) { 169 throw new IllegalArgumentException( 170 "Lower bound must be less than upper bound."); 171 } 172 this.lowerBound = bound; 173 notifyListeners(new DialLayerChangeEvent(this)); 174 } 175 176 /** 177 * Returns the upper bound of the dial range. 178 * 179 * @return The upper bound. 180 * 181 * @see #setUpperBound(double) 182 */ 183 public double getUpperBound() { 184 return this.upperBound; 185 } 186 187 /** 188 * Sets the upper bound of the dial range and sends a 189 * {@link DialLayerChangeEvent} to all registered listeners. 190 * 191 * @param bound the upper bound. 192 * 193 * @see #getUpperBound() 194 */ 195 public void setUpperBound(double bound) { 196 if (bound <= this.lowerBound) { 197 throw new IllegalArgumentException( 198 "Lower bound must be less than upper bound."); 199 } 200 this.upperBound = bound; 201 notifyListeners(new DialLayerChangeEvent(this)); 202 } 203 204 /** 205 * Sets the bounds for the range and sends a {@link DialLayerChangeEvent} 206 * to all registered listeners. 207 * 208 * @param lower the lower bound. 209 * @param upper the upper bound. 210 */ 211 public void setBounds(double lower, double upper) { 212 if (lower >= upper) { 213 throw new IllegalArgumentException( 214 "Lower must be less than upper."); 215 } 216 this.lowerBound = lower; 217 this.upperBound = upper; 218 notifyListeners(new DialLayerChangeEvent(this)); 219 } 220 221 /** 222 * Returns the paint used to highlight the range. 223 * 224 * @return The paint (never <code>null</code>). 225 * 226 * @see #setPaint(Paint) 227 */ 228 public Paint getPaint() { 229 return this.paint; 230 } 231 232 /** 233 * Sets the paint used to highlight the range and sends a 234 * {@link DialLayerChangeEvent} to all registered listeners. 235 * 236 * @param paint the paint (<code>null</code> not permitted). 237 * 238 * @see #getPaint() 239 */ 240 public void setPaint(Paint paint) { 241 if (paint == null) { 242 throw new IllegalArgumentException("Null 'paint' argument."); 243 } 244 this.paint = paint; 245 notifyListeners(new DialLayerChangeEvent(this)); 246 } 247 248 /** 249 * Returns the inner radius. 250 * 251 * @return The inner radius. 252 * 253 * @see #setInnerRadius(double) 254 */ 255 public double getInnerRadius() { 256 return this.innerRadius; 257 } 258 259 /** 260 * Sets the inner radius and sends a {@link DialLayerChangeEvent} to all 261 * registered listeners. 262 * 263 * @param radius the radius. 264 * 265 * @see #getInnerRadius() 266 */ 267 public void setInnerRadius(double radius) { 268 this.innerRadius = radius; 269 notifyListeners(new DialLayerChangeEvent(this)); 270 } 271 272 /** 273 * Returns the outer radius. 274 * 275 * @return The outer radius. 276 * 277 * @see #setOuterRadius(double) 278 */ 279 public double getOuterRadius() { 280 return this.outerRadius; 281 } 282 283 /** 284 * Sets the outer radius and sends a {@link DialLayerChangeEvent} to all 285 * registered listeners. 286 * 287 * @param radius the radius. 288 * 289 * @see #getOuterRadius() 290 */ 291 public void setOuterRadius(double radius) { 292 this.outerRadius = radius; 293 notifyListeners(new DialLayerChangeEvent(this)); 294 } 295 296 /** 297 * Returns <code>true</code> to indicate that this layer should be 298 * clipped within the dial window. 299 * 300 * @return <code>true</code>. 301 */ 302 public boolean isClippedToWindow() { 303 return true; 304 } 305 306 /** 307 * Draws the range. 308 * 309 * @param g2 the graphics target. 310 * @param plot the plot. 311 * @param frame the dial's reference frame (in Java2D space). 312 * @param view the dial's view rectangle (in Java2D space). 313 */ 314 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 315 Rectangle2D view) { 316 317 Rectangle2D arcRectInner = DialPlot.rectangleByRadius(frame, 318 this.innerRadius, this.innerRadius); 319 Rectangle2D arcRectOuter = DialPlot.rectangleByRadius(frame, 320 this.outerRadius, this.outerRadius); 321 322 DialScale scale = plot.getScale(this.scaleIndex); 323 if (scale == null) { 324 throw new RuntimeException("No scale for scaleIndex = " 325 + this.scaleIndex); 326 } 327 double angleMin = scale.valueToAngle(this.lowerBound); 328 double angleMax = scale.valueToAngle(this.upperBound); 329 330 Arc2D arcInner = new Arc2D.Double(arcRectInner, angleMin, 331 angleMax - angleMin, Arc2D.OPEN); 332 Arc2D arcOuter = new Arc2D.Double(arcRectOuter, angleMax, 333 angleMin - angleMax, Arc2D.OPEN); 334 335 g2.setPaint(this.paint); 336 g2.setStroke(new BasicStroke(2.0f)); 337 g2.draw(arcInner); 338 g2.draw(arcOuter); 339 } 340 341 /** 342 * Tests this instance for equality with an arbitrary object. 343 * 344 * @param obj the object (<code>null</code> permitted). 345 * 346 * @return A boolean. 347 */ 348 public boolean equals(Object obj) { 349 if (obj == this) { 350 return true; 351 } 352 if (!(obj instanceof StandardDialRange)) { 353 return false; 354 } 355 StandardDialRange that = (StandardDialRange) obj; 356 if (this.scaleIndex != that.scaleIndex) { 357 return false; 358 } 359 if (this.lowerBound != that.lowerBound) { 360 return false; 361 } 362 if (this.upperBound != that.upperBound) { 363 return false; 364 } 365 if (!PaintUtilities.equal(this.paint, that.paint)) { 366 return false; 367 } 368 if (this.innerRadius != that.innerRadius) { 369 return false; 370 } 371 if (this.outerRadius != that.outerRadius) { 372 return false; 373 } 374 return super.equals(obj); 375 } 376 377 /** 378 * Returns a hash code for this instance. 379 * 380 * @return The hash code. 381 */ 382 public int hashCode() { 383 int result = 193; 384 long temp = Double.doubleToLongBits(this.lowerBound); 385 result = 37 * result + (int) (temp ^ (temp >>> 32)); 386 temp = Double.doubleToLongBits(this.upperBound); 387 result = 37 * result + (int) (temp ^ (temp >>> 32)); 388 temp = Double.doubleToLongBits(this.innerRadius); 389 result = 37 * result + (int) (temp ^ (temp >>> 32)); 390 temp = Double.doubleToLongBits(this.outerRadius); 391 result = 37 * result + (int) (temp ^ (temp >>> 32)); 392 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint); 393 return result; 394 } 395 396 /** 397 * Returns a clone of this instance. 398 * 399 * @return A clone. 400 * 401 * @throws CloneNotSupportedException if any of the attributes of this 402 * instance cannot be cloned. 403 */ 404 public Object clone() throws CloneNotSupportedException { 405 return super.clone(); 406 } 407 408 /** 409 * Provides serialization support. 410 * 411 * @param stream the output stream. 412 * 413 * @throws IOException if there is an I/O error. 414 */ 415 private void writeObject(ObjectOutputStream stream) throws IOException { 416 stream.defaultWriteObject(); 417 SerialUtilities.writePaint(this.paint, stream); 418 } 419 420 /** 421 * Provides serialization support. 422 * 423 * @param stream the input stream. 424 * 425 * @throws IOException if there is an I/O error. 426 * @throws ClassNotFoundException if there is a classpath problem. 427 */ 428 private void readObject(ObjectInputStream stream) 429 throws IOException, ClassNotFoundException { 430 stream.defaultReadObject(); 431 this.paint = SerialUtilities.readPaint(stream); 432 } 433 434}