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