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