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