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