001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2017, 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 * DialPointer.java 029 * ---------------- 030 * (C) Copyright 2006-2017, 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 * 17-Oct-2007 : Added equals() overrides (DG); 039 * 24-Oct-2007 : Implemented PublicCloneable, changed default radius, 040 * and added argument checks (DG); 041 * 23-Nov-2007 : Added fillPaint and outlinePaint attributes to 042 * DialPointer.Pointer (DG); 043 * 03-Jul-2013 : Use ParamChecks (DG); 044 * 045 */ 046 047package org.jfree.chart.plot.dial; 048 049import java.awt.BasicStroke; 050import java.awt.Color; 051import java.awt.Graphics2D; 052import java.awt.Paint; 053import java.awt.Stroke; 054import java.awt.geom.Arc2D; 055import java.awt.geom.GeneralPath; 056import java.awt.geom.Line2D; 057import java.awt.geom.Point2D; 058import java.awt.geom.Rectangle2D; 059import java.io.IOException; 060import java.io.ObjectInputStream; 061import java.io.ObjectOutputStream; 062import java.io.Serializable; 063import org.jfree.chart.HashUtils; 064import org.jfree.chart.util.PaintUtils; 065import org.jfree.chart.util.Args; 066import org.jfree.chart.util.PublicCloneable; 067import org.jfree.chart.util.SerialUtils; 068 069/** 070 * A base class for the pointer in a {@link DialPlot}. 071 * 072 * @since 1.0.7 073 */ 074public abstract class DialPointer extends AbstractDialLayer 075 implements DialLayer, Cloneable, PublicCloneable, Serializable { 076 077 /** The needle radius. */ 078 double radius; 079 080 /** 081 * The dataset index for the needle. 082 */ 083 int datasetIndex; 084 085 /** 086 * Creates a new {@code DialPointer} instance. 087 */ 088 protected DialPointer() { 089 this(0); 090 } 091 092 /** 093 * Creates a new pointer for the specified dataset. 094 * 095 * @param datasetIndex the dataset index. 096 */ 097 protected DialPointer(int datasetIndex) { 098 this.radius = 0.9; 099 this.datasetIndex = datasetIndex; 100 } 101 102 /** 103 * Returns the dataset index that the pointer maps to. 104 * 105 * @return The dataset index. 106 * 107 * @see #getDatasetIndex() 108 */ 109 public int getDatasetIndex() { 110 return this.datasetIndex; 111 } 112 113 /** 114 * Sets the dataset index for the pointer and sends a 115 * {@link DialLayerChangeEvent} to all registered listeners. 116 * 117 * @param index the index. 118 * 119 * @see #getDatasetIndex() 120 */ 121 public void setDatasetIndex(int index) { 122 this.datasetIndex = index; 123 notifyListeners(new DialLayerChangeEvent(this)); 124 } 125 126 /** 127 * Returns the radius of the pointer, as a percentage of the dial's 128 * framing rectangle. 129 * 130 * @return The radius. 131 * 132 * @see #setRadius(double) 133 */ 134 public double getRadius() { 135 return this.radius; 136 } 137 138 /** 139 * Sets the radius of the pointer and sends a 140 * {@link DialLayerChangeEvent} to all registered listeners. 141 * 142 * @param radius the radius. 143 * 144 * @see #getRadius() 145 */ 146 public void setRadius(double radius) { 147 this.radius = radius; 148 notifyListeners(new DialLayerChangeEvent(this)); 149 } 150 151 /** 152 * Returns {@code true} to indicate that this layer should be 153 * clipped within the dial window. 154 * 155 * @return {@code true}. 156 */ 157 @Override 158 public boolean isClippedToWindow() { 159 return true; 160 } 161 162 /** 163 * Checks this instance for equality with an arbitrary object. 164 * 165 * @param obj the object ({@code null} not permitted). 166 * 167 * @return A boolean. 168 */ 169 @Override 170 public boolean equals(Object obj) { 171 if (obj == this) { 172 return true; 173 } 174 if (!(obj instanceof DialPointer)) { 175 return false; 176 } 177 DialPointer that = (DialPointer) obj; 178 if (this.datasetIndex != that.datasetIndex) { 179 return false; 180 } 181 if (this.radius != that.radius) { 182 return false; 183 } 184 return super.equals(obj); 185 } 186 187 /** 188 * Returns a hash code. 189 * 190 * @return A hash code. 191 */ 192 @Override 193 public int hashCode() { 194 int result = 23; 195 result = HashUtils.hashCode(result, this.radius); 196 return result; 197 } 198 199 /** 200 * Returns a clone of the pointer. 201 * 202 * @return a clone. 203 * 204 * @throws CloneNotSupportedException if one of the attributes cannot 205 * be cloned. 206 */ 207 @Override 208 public Object clone() throws CloneNotSupportedException { 209 return super.clone(); 210 } 211 212 /** 213 * A dial pointer that draws a thin line (like a pin). 214 */ 215 public static class Pin extends DialPointer { 216 217 /** For serialization. */ 218 static final long serialVersionUID = -8445860485367689750L; 219 220 /** The paint. */ 221 private transient Paint paint; 222 223 /** The stroke. */ 224 private transient Stroke stroke; 225 226 /** 227 * Creates a new instance. 228 */ 229 public Pin() { 230 this(0); 231 } 232 233 /** 234 * Creates a new instance. 235 * 236 * @param datasetIndex the dataset index. 237 */ 238 public Pin(int datasetIndex) { 239 super(datasetIndex); 240 this.paint = Color.RED; 241 this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, 242 BasicStroke.JOIN_BEVEL); 243 } 244 245 /** 246 * Returns the paint. 247 * 248 * @return The paint (never {@code null}). 249 * 250 * @see #setPaint(Paint) 251 */ 252 public Paint getPaint() { 253 return this.paint; 254 } 255 256 /** 257 * Sets the paint and sends a {@link DialLayerChangeEvent} to all 258 * registered listeners. 259 * 260 * @param paint the paint ({@code null} not permitted). 261 * 262 * @see #getPaint() 263 */ 264 public void setPaint(Paint paint) { 265 Args.nullNotPermitted(paint, "paint"); 266 this.paint = paint; 267 notifyListeners(new DialLayerChangeEvent(this)); 268 } 269 270 /** 271 * Returns the stroke. 272 * 273 * @return The stroke (never {@code null}). 274 * 275 * @see #setStroke(Stroke) 276 */ 277 public Stroke getStroke() { 278 return this.stroke; 279 } 280 281 /** 282 * Sets the stroke and sends a {@link DialLayerChangeEvent} to all 283 * registered listeners. 284 * 285 * @param stroke the stroke ({@code null} not permitted). 286 * 287 * @see #getStroke() 288 */ 289 public void setStroke(Stroke stroke) { 290 Args.nullNotPermitted(stroke, "stroke"); 291 this.stroke = stroke; 292 notifyListeners(new DialLayerChangeEvent(this)); 293 } 294 295 /** 296 * Draws the pointer. 297 * 298 * @param g2 the graphics target. 299 * @param plot the plot. 300 * @param frame the dial's reference frame. 301 * @param view the dial's view. 302 */ 303 @Override 304 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 305 Rectangle2D view) { 306 307 g2.setPaint(this.paint); 308 g2.setStroke(this.stroke); 309 Rectangle2D arcRect = DialPlot.rectangleByRadius(frame, 310 this.radius, this.radius); 311 312 double value = plot.getValue(this.datasetIndex); 313 DialScale scale = plot.getScaleForDataset(this.datasetIndex); 314 double angle = scale.valueToAngle(value); 315 316 Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN); 317 Point2D pt = arc.getEndPoint(); 318 319 Line2D line = new Line2D.Double(frame.getCenterX(), 320 frame.getCenterY(), pt.getX(), pt.getY()); 321 g2.draw(line); 322 } 323 324 /** 325 * Tests this pointer for equality with an arbitrary object. 326 * 327 * @param obj the object ({@code null} permitted). 328 * 329 * @return A boolean. 330 */ 331 @Override 332 public boolean equals(Object obj) { 333 if (obj == this) { 334 return true; 335 } 336 if (!(obj instanceof DialPointer.Pin)) { 337 return false; 338 } 339 DialPointer.Pin that = (DialPointer.Pin) obj; 340 if (!PaintUtils.equal(this.paint, that.paint)) { 341 return false; 342 } 343 if (!this.stroke.equals(that.stroke)) { 344 return false; 345 } 346 return super.equals(obj); 347 } 348 349 /** 350 * Returns a hash code for this instance. 351 * 352 * @return A hash code. 353 */ 354 @Override 355 public int hashCode() { 356 int result = super.hashCode(); 357 result = HashUtils.hashCode(result, this.paint); 358 result = HashUtils.hashCode(result, this.stroke); 359 return result; 360 } 361 362 /** 363 * Provides serialization support. 364 * 365 * @param stream the output stream. 366 * 367 * @throws IOException if there is an I/O error. 368 */ 369 private void writeObject(ObjectOutputStream stream) throws IOException { 370 stream.defaultWriteObject(); 371 SerialUtils.writePaint(this.paint, stream); 372 SerialUtils.writeStroke(this.stroke, stream); 373 } 374 375 /** 376 * Provides serialization support. 377 * 378 * @param stream the input stream. 379 * 380 * @throws IOException if there is an I/O error. 381 * @throws ClassNotFoundException if there is a classpath problem. 382 */ 383 private void readObject(ObjectInputStream stream) 384 throws IOException, ClassNotFoundException { 385 stream.defaultReadObject(); 386 this.paint = SerialUtils.readPaint(stream); 387 this.stroke = SerialUtils.readStroke(stream); 388 } 389 390 } 391 392 /** 393 * A dial pointer. 394 */ 395 public static class Pointer extends DialPointer { 396 397 /** For serialization. */ 398 static final long serialVersionUID = -4180500011963176960L; 399 400 /** 401 * The radius that defines the width of the pointer at the base. 402 */ 403 private double widthRadius; 404 405 /** 406 * The fill paint. 407 * 408 * @since 1.0.8 409 */ 410 private transient Paint fillPaint; 411 412 /** 413 * The outline paint. 414 * 415 * @since 1.0.8 416 */ 417 private transient Paint outlinePaint; 418 419 /** 420 * Creates a new instance. 421 */ 422 public Pointer() { 423 this(0); 424 } 425 426 /** 427 * Creates a new instance. 428 * 429 * @param datasetIndex the dataset index. 430 */ 431 public Pointer(int datasetIndex) { 432 super(datasetIndex); 433 this.widthRadius = 0.05; 434 this.fillPaint = Color.GRAY; 435 this.outlinePaint = Color.BLACK; 436 } 437 438 /** 439 * Returns the width radius. 440 * 441 * @return The width radius. 442 * 443 * @see #setWidthRadius(double) 444 */ 445 public double getWidthRadius() { 446 return this.widthRadius; 447 } 448 449 /** 450 * Sets the width radius and sends a {@link DialLayerChangeEvent} to 451 * all registered listeners. 452 * 453 * @param radius the radius 454 * 455 * @see #getWidthRadius() 456 */ 457 public void setWidthRadius(double radius) { 458 this.widthRadius = radius; 459 notifyListeners(new DialLayerChangeEvent(this)); 460 } 461 462 /** 463 * Returns the fill paint. 464 * 465 * @return The paint (never {@code null}). 466 * 467 * @see #setFillPaint(Paint) 468 * 469 * @since 1.0.8 470 */ 471 public Paint getFillPaint() { 472 return this.fillPaint; 473 } 474 475 /** 476 * Sets the fill paint and sends a {@link DialLayerChangeEvent} to all 477 * registered listeners. 478 * 479 * @param paint the paint ({@code null} not permitted). 480 * 481 * @see #getFillPaint() 482 * 483 * @since 1.0.8 484 */ 485 public void setFillPaint(Paint paint) { 486 Args.nullNotPermitted(paint, "paint"); 487 this.fillPaint = paint; 488 notifyListeners(new DialLayerChangeEvent(this)); 489 } 490 491 /** 492 * Returns the outline paint. 493 * 494 * @return The paint (never {@code null}). 495 * 496 * @see #setOutlinePaint(Paint) 497 * 498 * @since 1.0.8 499 */ 500 public Paint getOutlinePaint() { 501 return this.outlinePaint; 502 } 503 504 /** 505 * Sets the outline paint and sends a {@link DialLayerChangeEvent} to 506 * all registered listeners. 507 * 508 * @param paint the paint ({@code null} not permitted). 509 * 510 * @see #getOutlinePaint() 511 * 512 * @since 1.0.8 513 */ 514 public void setOutlinePaint(Paint paint) { 515 Args.nullNotPermitted(paint, "paint"); 516 this.outlinePaint = paint; 517 notifyListeners(new DialLayerChangeEvent(this)); 518 } 519 520 /** 521 * Draws the pointer. 522 * 523 * @param g2 the graphics target. 524 * @param plot the plot. 525 * @param frame the dial's reference frame. 526 * @param view the dial's view. 527 */ 528 @Override 529 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 530 Rectangle2D view) { 531 532 g2.setPaint(Color.BLUE); 533 g2.setStroke(new BasicStroke(1.0f)); 534 Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame, 535 this.radius, this.radius); 536 Rectangle2D widthRect = DialPlot.rectangleByRadius(frame, 537 this.widthRadius, this.widthRadius); 538 double value = plot.getValue(this.datasetIndex); 539 DialScale scale = plot.getScaleForDataset(this.datasetIndex); 540 double angle = scale.valueToAngle(value); 541 542 Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN); 543 Point2D pt1 = arc1.getEndPoint(); 544 Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0, 545 Arc2D.OPEN); 546 Point2D pt2 = arc2.getStartPoint(); 547 Point2D pt3 = arc2.getEndPoint(); 548 Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0, 549 Arc2D.OPEN); 550 Point2D pt4 = arc3.getStartPoint(); 551 552 GeneralPath gp = new GeneralPath(); 553 gp.moveTo((float) pt1.getX(), (float) pt1.getY()); 554 gp.lineTo((float) pt2.getX(), (float) pt2.getY()); 555 gp.lineTo((float) pt4.getX(), (float) pt4.getY()); 556 gp.lineTo((float) pt3.getX(), (float) pt3.getY()); 557 gp.closePath(); 558 g2.setPaint(this.fillPaint); 559 g2.fill(gp); 560 561 g2.setPaint(this.outlinePaint); 562 Line2D line = new Line2D.Double(frame.getCenterX(), 563 frame.getCenterY(), pt1.getX(), pt1.getY()); 564 g2.draw(line); 565 566 line.setLine(pt2, pt3); 567 g2.draw(line); 568 569 line.setLine(pt3, pt1); 570 g2.draw(line); 571 572 line.setLine(pt2, pt1); 573 g2.draw(line); 574 575 line.setLine(pt2, pt4); 576 g2.draw(line); 577 578 line.setLine(pt3, pt4); 579 g2.draw(line); 580 } 581 582 /** 583 * Tests this pointer for equality with an arbitrary object. 584 * 585 * @param obj the object ({@code null} permitted). 586 * 587 * @return A boolean. 588 */ 589 @Override 590 public boolean equals(Object obj) { 591 if (obj == this) { 592 return true; 593 } 594 if (!(obj instanceof DialPointer.Pointer)) { 595 return false; 596 } 597 DialPointer.Pointer that = (DialPointer.Pointer) obj; 598 599 if (this.widthRadius != that.widthRadius) { 600 return false; 601 } 602 if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) { 603 return false; 604 } 605 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 606 return false; 607 } 608 return super.equals(obj); 609 } 610 611 /** 612 * Returns a hash code for this instance. 613 * 614 * @return A hash code. 615 */ 616 @Override 617 public int hashCode() { 618 int result = super.hashCode(); 619 result = HashUtils.hashCode(result, this.widthRadius); 620 result = HashUtils.hashCode(result, this.fillPaint); 621 result = HashUtils.hashCode(result, this.outlinePaint); 622 return result; 623 } 624 625 /** 626 * Provides serialization support. 627 * 628 * @param stream the output stream. 629 * 630 * @throws IOException if there is an I/O error. 631 */ 632 private void writeObject(ObjectOutputStream stream) throws IOException { 633 stream.defaultWriteObject(); 634 SerialUtils.writePaint(this.fillPaint, stream); 635 SerialUtils.writePaint(this.outlinePaint, stream); 636 } 637 638 /** 639 * Provides serialization support. 640 * 641 * @param stream the input stream. 642 * 643 * @throws IOException if there is an I/O error. 644 * @throws ClassNotFoundException if there is a classpath problem. 645 */ 646 private void readObject(ObjectInputStream stream) 647 throws IOException, ClassNotFoundException { 648 stream.defaultReadObject(); 649 this.fillPaint = SerialUtils.readPaint(stream); 650 this.outlinePaint = SerialUtils.readPaint(stream); 651 } 652 653 } 654 655}