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