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 * ArcDialFrame.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 : Updated equals() (DG); 040 * 24-Oct-2007 : Added argument checks and API docs, and renamed 041 * StandardDialFrame --> ArcDialFrame (DG); 042 * 043 */ 044 045package org.jfree.chart.plot.dial; 046 047import java.awt.BasicStroke; 048import java.awt.Color; 049import java.awt.Graphics2D; 050import java.awt.Paint; 051import java.awt.Shape; 052import java.awt.Stroke; 053import java.awt.geom.Arc2D; 054import java.awt.geom.Area; 055import java.awt.geom.GeneralPath; 056import java.awt.geom.Point2D; 057import java.awt.geom.Rectangle2D; 058import java.io.IOException; 059import java.io.ObjectInputStream; 060import java.io.ObjectOutputStream; 061import java.io.Serializable; 062 063import org.jfree.chart.HashUtilities; 064import org.jfree.io.SerialUtilities; 065import org.jfree.util.PaintUtilities; 066import org.jfree.util.PublicCloneable; 067 068/** 069 * A standard frame for the {@link DialPlot} class. 070 */ 071public class ArcDialFrame extends AbstractDialLayer implements DialFrame, 072 Cloneable, PublicCloneable, Serializable { 073 074 /** For serialization. */ 075 static final long serialVersionUID = -4089176959553523499L; 076 077 /** 078 * The color used for the front of the panel. This field is transient 079 * because it requires special handling for serialization. 080 */ 081 private transient Paint backgroundPaint; 082 083 /** 084 * The color used for the border around the window. This field is transient 085 * because it requires special handling for serialization. 086 */ 087 private transient Paint foregroundPaint; 088 089 /** 090 * The stroke for drawing the frame outline. This field is transient 091 * because it requires special handling for serialization. 092 */ 093 private transient Stroke stroke; 094 095 /** 096 * The start angle. 097 */ 098 private double startAngle; 099 100 /** 101 * The end angle. 102 */ 103 private double extent; 104 105 /** The inner radius, relative to the framing rectangle. */ 106 private double innerRadius; 107 108 /** The outer radius, relative to the framing rectangle. */ 109 private double outerRadius; 110 111 /** 112 * Creates a new instance of <code>ArcDialFrame</code> that spans 113 * 180 degrees. 114 */ 115 public ArcDialFrame() { 116 this(0, 180); 117 } 118 119 /** 120 * Creates a new instance of <code>ArcDialFrame</code> that spans 121 * the arc specified. 122 * 123 * @param startAngle the startAngle (in degrees). 124 * @param extent the extent of the arc (in degrees, counter-clockwise). 125 */ 126 public ArcDialFrame(double startAngle, double extent) { 127 this.backgroundPaint = Color.gray; 128 this.foregroundPaint = new Color(100, 100, 150); 129 this.stroke = new BasicStroke(2.0f); 130 this.innerRadius = 0.25; 131 this.outerRadius = 0.75; 132 this.startAngle = startAngle; 133 this.extent = extent; 134 } 135 136 /** 137 * Returns the background paint (never <code>null</code>). 138 * 139 * @return The background paint. 140 * 141 * @see #setBackgroundPaint(Paint) 142 */ 143 public Paint getBackgroundPaint() { 144 return this.backgroundPaint; 145 } 146 147 /** 148 * Sets the background paint and sends a {@link DialLayerChangeEvent} to 149 * all registered listeners. 150 * 151 * @param paint the paint (<code>null</code> not permitted). 152 * 153 * @see #getBackgroundPaint() 154 */ 155 public void setBackgroundPaint(Paint paint) { 156 if (paint == null) { 157 throw new IllegalArgumentException("Null 'paint' argument."); 158 } 159 this.backgroundPaint = paint; 160 notifyListeners(new DialLayerChangeEvent(this)); 161 } 162 163 /** 164 * Returns the foreground paint. 165 * 166 * @return The foreground paint (never <code>null</code>). 167 * 168 * @see #setForegroundPaint(Paint) 169 */ 170 public Paint getForegroundPaint() { 171 return this.foregroundPaint; 172 } 173 174 /** 175 * Sets the foreground paint and sends a {@link DialLayerChangeEvent} to 176 * all registered listeners. 177 * 178 * @param paint the paint (<code>null</code> not permitted). 179 * 180 * @see #getForegroundPaint() 181 */ 182 public void setForegroundPaint(Paint paint) { 183 if (paint == null) { 184 throw new IllegalArgumentException("Null 'paint' argument."); 185 } 186 this.foregroundPaint = paint; 187 notifyListeners(new DialLayerChangeEvent(this)); 188 } 189 190 /** 191 * Returns the stroke. 192 * 193 * @return The stroke (never <code>null</code>). 194 * 195 * @see #setStroke(Stroke) 196 */ 197 public Stroke getStroke() { 198 return this.stroke; 199 } 200 201 /** 202 * Sets the stroke and sends a {@link DialLayerChangeEvent} to 203 * all registered listeners. 204 * 205 * @param stroke the stroke (<code>null</code> not permitted). 206 * 207 * @see #getStroke() 208 */ 209 public void setStroke(Stroke stroke) { 210 if (stroke == null) { 211 throw new IllegalArgumentException("Null 'stroke' argument."); 212 } 213 this.stroke = stroke; 214 notifyListeners(new DialLayerChangeEvent(this)); 215 } 216 217 /** 218 * Returns the inner radius, relative to the framing rectangle. 219 * 220 * @return The inner radius. 221 * 222 * @see #setInnerRadius(double) 223 */ 224 public double getInnerRadius() { 225 return this.innerRadius; 226 } 227 228 /** 229 * Sets the inner radius and sends a {@link DialLayerChangeEvent} to 230 * all registered listeners. 231 * 232 * @param radius the inner radius. 233 * 234 * @see #getInnerRadius() 235 */ 236 public void setInnerRadius(double radius) { 237 if (radius < 0.0) { 238 throw new IllegalArgumentException("Negative 'radius' argument."); 239 } 240 this.innerRadius = radius; 241 notifyListeners(new DialLayerChangeEvent(this)); 242 } 243 244 /** 245 * Returns the outer radius, relative to the framing rectangle. 246 * 247 * @return The outer radius. 248 * 249 * @see #setOuterRadius(double) 250 */ 251 public double getOuterRadius() { 252 return this.outerRadius; 253 } 254 255 /** 256 * Sets the outer radius and sends a {@link DialLayerChangeEvent} to 257 * all registered listeners. 258 * 259 * @param radius the outer radius. 260 * 261 * @see #getOuterRadius() 262 */ 263 public void setOuterRadius(double radius) { 264 if (radius < 0.0) { 265 throw new IllegalArgumentException("Negative 'radius' argument."); 266 } 267 this.outerRadius = radius; 268 notifyListeners(new DialLayerChangeEvent(this)); 269 } 270 271 /** 272 * Returns the start angle. 273 * 274 * @return The start angle. 275 * 276 * @see #setStartAngle(double) 277 */ 278 public double getStartAngle() { 279 return this.startAngle; 280 } 281 282 /** 283 * Sets the start angle and sends a {@link DialLayerChangeEvent} to 284 * all registered listeners. 285 * 286 * @param angle the angle. 287 * 288 * @see #getStartAngle() 289 */ 290 public void setStartAngle(double angle) { 291 this.startAngle = angle; 292 notifyListeners(new DialLayerChangeEvent(this)); 293 } 294 295 /** 296 * Returns the extent. 297 * 298 * @return The extent. 299 * 300 * @see #setExtent(double) 301 */ 302 public double getExtent() { 303 return this.extent; 304 } 305 306 /** 307 * Sets the extent and sends a {@link DialLayerChangeEvent} to 308 * all registered listeners. 309 * 310 * @param extent the extent. 311 * 312 * @see #getExtent() 313 */ 314 public void setExtent(double extent) { 315 this.extent = extent; 316 notifyListeners(new DialLayerChangeEvent(this)); 317 } 318 319 /** 320 * Returns the shape for the window for this dial. Some dial layers will 321 * request that their drawing be clipped within this window. 322 * 323 * @param frame the reference frame (<code>null</code> not permitted). 324 * 325 * @return The shape of the dial's window. 326 */ 327 public Shape getWindow(Rectangle2D frame) { 328 329 Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame, 330 this.innerRadius, this.innerRadius); 331 Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame, 332 this.outerRadius, this.outerRadius); 333 Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle, 334 this.extent, Arc2D.OPEN); 335 Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle 336 + this.extent, -this.extent, Arc2D.OPEN); 337 GeneralPath p = new GeneralPath(); 338 Point2D point1 = inner.getStartPoint(); 339 p.moveTo((float) point1.getX(), (float) point1.getY()); 340 p.append(inner, true); 341 p.append(outer, true); 342 p.closePath(); 343 return p; 344 345 } 346 347 /** 348 * Returns the outer window. 349 * 350 * @param frame the frame. 351 * 352 * @return The outer window. 353 */ 354 protected Shape getOuterWindow(Rectangle2D frame) { 355 double radiusMargin = 0.02; 356 double angleMargin = 1.5; 357 Rectangle2D innerFrame = DialPlot.rectangleByRadius(frame, 358 this.innerRadius - radiusMargin, this.innerRadius 359 - radiusMargin); 360 Rectangle2D outerFrame = DialPlot.rectangleByRadius(frame, 361 this.outerRadius + radiusMargin, this.outerRadius 362 + radiusMargin); 363 Arc2D inner = new Arc2D.Double(innerFrame, this.startAngle 364 - angleMargin, this.extent + 2 * angleMargin, Arc2D.OPEN); 365 Arc2D outer = new Arc2D.Double(outerFrame, this.startAngle 366 + angleMargin + this.extent, -this.extent - 2 * angleMargin, 367 Arc2D.OPEN); 368 GeneralPath p = new GeneralPath(); 369 Point2D point1 = inner.getStartPoint(); 370 p.moveTo((float) point1.getX(), (float) point1.getY()); 371 p.append(inner, true); 372 p.append(outer, true); 373 p.closePath(); 374 return p; 375 } 376 377 /** 378 * Draws the frame. 379 * 380 * @param g2 the graphics target. 381 * @param plot the plot. 382 * @param frame the dial's reference frame. 383 * @param view the dial's view rectangle. 384 */ 385 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 386 Rectangle2D view) { 387 388 Shape window = getWindow(frame); 389 Shape outerWindow = getOuterWindow(frame); 390 391 Area area1 = new Area(outerWindow); 392 Area area2 = new Area(window); 393 area1.subtract(area2); 394 g2.setPaint(Color.lightGray); 395 g2.fill(area1); 396 397 g2.setStroke(this.stroke); 398 g2.setPaint(this.foregroundPaint); 399 g2.draw(window); 400 g2.draw(outerWindow); 401 402 403 } 404 405 /** 406 * Returns <code>false</code> to indicate that this dial layer is not 407 * clipped to the dial window. 408 * 409 * @return <code>false</code>. 410 */ 411 public boolean isClippedToWindow() { 412 return false; 413 } 414 415 /** 416 * Tests this instance for equality with an arbitrary object. 417 * 418 * @param obj the object (<code>null</code> permitted). 419 * 420 * @return A boolean. 421 */ 422 public boolean equals(Object obj) { 423 if (obj == this) { 424 return true; 425 } 426 if (!(obj instanceof ArcDialFrame)) { 427 return false; 428 } 429 ArcDialFrame that = (ArcDialFrame) obj; 430 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 431 return false; 432 } 433 if (!PaintUtilities.equal(this.foregroundPaint, that.foregroundPaint)) { 434 return false; 435 } 436 if (this.startAngle != that.startAngle) { 437 return false; 438 } 439 if (this.extent != that.extent) { 440 return false; 441 } 442 if (this.innerRadius != that.innerRadius) { 443 return false; 444 } 445 if (this.outerRadius != that.outerRadius) { 446 return false; 447 } 448 if (!this.stroke.equals(that.stroke)) { 449 return false; 450 } 451 return super.equals(obj); 452 } 453 454 /** 455 * Returns a hash code for this instance. 456 * 457 * @return The hash code. 458 */ 459 public int hashCode() { 460 int result = 193; 461 long temp = Double.doubleToLongBits(this.startAngle); 462 result = 37 * result + (int) (temp ^ (temp >>> 32)); 463 temp = Double.doubleToLongBits(this.extent); 464 result = 37 * result + (int) (temp ^ (temp >>> 32)); 465 temp = Double.doubleToLongBits(this.innerRadius); 466 result = 37 * result + (int) (temp ^ (temp >>> 32)); 467 temp = Double.doubleToLongBits(this.outerRadius); 468 result = 37 * result + (int) (temp ^ (temp >>> 32)); 469 result = 37 * result + HashUtilities.hashCodeForPaint( 470 this.backgroundPaint); 471 result = 37 * result + HashUtilities.hashCodeForPaint( 472 this.foregroundPaint); 473 result = 37 * result + this.stroke.hashCode(); 474 return result; 475 } 476 477 /** 478 * Returns a clone of this instance. 479 * 480 * @return A clone. 481 * 482 * @throws CloneNotSupportedException if any attribute of this instance 483 * cannot be cloned. 484 */ 485 public Object clone() throws CloneNotSupportedException { 486 return super.clone(); 487 } 488 489 /** 490 * Provides serialization support. 491 * 492 * @param stream the output stream. 493 * 494 * @throws IOException if there is an I/O error. 495 */ 496 private void writeObject(ObjectOutputStream stream) throws IOException { 497 stream.defaultWriteObject(); 498 SerialUtilities.writePaint(this.backgroundPaint, stream); 499 SerialUtilities.writePaint(this.foregroundPaint, stream); 500 SerialUtilities.writeStroke(this.stroke, stream); 501 } 502 503 /** 504 * Provides serialization support. 505 * 506 * @param stream the input stream. 507 * 508 * @throws IOException if there is an I/O error. 509 * @throws ClassNotFoundException if there is a classpath problem. 510 */ 511 private void readObject(ObjectInputStream stream) 512 throws IOException, ClassNotFoundException { 513 stream.defaultReadObject(); 514 this.backgroundPaint = SerialUtilities.readPaint(stream); 515 this.foregroundPaint = SerialUtilities.readPaint(stream); 516 this.stroke = SerialUtilities.readStroke(stream); 517 } 518 519}