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