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