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