001/* ====================================================== 002 * JFreeChart : a chart library for the Java(tm) platform 003 * ====================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: https://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 * Crosshair.java 029 * -------------- 030 * (C) Copyright 2009-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): -; 034 * 035 */ 036 037package org.jfree.chart.plot; 038 039import org.jfree.chart.HashUtils; 040import org.jfree.chart.labels.CrosshairLabelGenerator; 041import org.jfree.chart.labels.StandardCrosshairLabelGenerator; 042import org.jfree.chart.panel.CrosshairOverlay; 043import org.jfree.chart.ui.RectangleAnchor; 044import org.jfree.chart.ui.RectangleInsets; 045import org.jfree.chart.util.Args; 046import org.jfree.chart.util.PaintUtils; 047import org.jfree.chart.util.PublicCloneable; 048import org.jfree.chart.util.SerialUtils; 049 050import java.awt.*; 051import java.beans.PropertyChangeListener; 052import java.beans.PropertyChangeSupport; 053import java.io.IOException; 054import java.io.ObjectInputStream; 055import java.io.ObjectOutputStream; 056import java.io.Serializable; 057 058/** 059 * A {@code Crosshair} represents a value on a chart and is usually displayed 060 * as a line perpendicular to the x or y-axis (and sometimes including a label 061 * that shows the crosshair value as text). Instances of this class are used 062 * to store the cross hair value plus the visual characteristics of the line 063 * that will be rendered once the instance is added to a 064 * {@link CrosshairOverlay} (or {@code CrosshairOverlaydFX} if you are using 065 * the JavaFX extensions for JFreeChart). 066 * <br><br> 067 * Crosshairs support a property change mechanism which is used by JFreeChart 068 * to automatically repaint the overlay whenever a crosshair attribute is 069 * updated. 070 */ 071public class Crosshair implements Cloneable, PublicCloneable, Serializable { 072 073 /** Flag controlling visibility. */ 074 private boolean visible; 075 076 /** The crosshair value. */ 077 private double value; 078 079 /** The paint for the crosshair line. */ 080 private transient Paint paint; 081 082 /** The stroke for the crosshair line. */ 083 private transient Stroke stroke; 084 085 /** 086 * A flag that controls whether the crosshair has a label visible. 087 */ 088 private boolean labelVisible; 089 090 /** 091 * The label anchor. 092 */ 093 private RectangleAnchor labelAnchor; 094 095 /** A label generator. */ 096 private CrosshairLabelGenerator labelGenerator; 097 098 /** 099 * The x-offset in Java2D units. 100 */ 101 private double labelXOffset; 102 103 /** 104 * The y-offset in Java2D units. 105 */ 106 private double labelYOffset; 107 108 /** 109 * The label padding. 110 */ 111 private RectangleInsets labelPadding; 112 113 /** 114 * The label font. 115 */ 116 private Font labelFont; 117 118 /** 119 * The label paint. 120 */ 121 private transient Paint labelPaint; 122 123 /** 124 * The label background paint. 125 */ 126 private transient Paint labelBackgroundPaint; 127 128 /** A flag that controls the visibility of the label outline. */ 129 private boolean labelOutlineVisible; 130 131 /** The label outline stroke. */ 132 private transient Stroke labelOutlineStroke; 133 134 /** The label outline paint. */ 135 private transient Paint labelOutlinePaint; 136 137 /** Property change support. */ 138 private transient PropertyChangeSupport pcs; 139 140 /** 141 * Creates a new crosshair with value 0.0. 142 */ 143 public Crosshair() { 144 this(0.0); 145 } 146 147 /** 148 * Creates a new crosshair with the specified value. 149 * 150 * @param value the value. 151 */ 152 public Crosshair(double value) { 153 this(value, Color.BLACK, new BasicStroke(1.0f)); 154 } 155 156 /** 157 * Creates a new crosshair value with the specified value and line style. 158 * 159 * @param value the value. 160 * @param paint the line paint ({@code null} not permitted). 161 * @param stroke the line stroke ({@code null} not permitted). 162 */ 163 public Crosshair(double value, Paint paint, Stroke stroke) { 164 Args.nullNotPermitted(paint, "paint"); 165 Args.nullNotPermitted(stroke, "stroke"); 166 this.visible = true; 167 this.value = value; 168 this.paint = paint; 169 this.stroke = stroke; 170 this.labelVisible = false; 171 this.labelGenerator = new StandardCrosshairLabelGenerator(); 172 this.labelAnchor = RectangleAnchor.BOTTOM_LEFT; 173 this.labelXOffset = 5.0; 174 this.labelYOffset = 5.0; 175 this.labelPadding = RectangleInsets.ZERO_INSETS; 176 this.labelFont = new Font("Tahoma", Font.PLAIN, 12); 177 this.labelPaint = Color.BLACK; 178 this.labelBackgroundPaint = new Color(0, 0, 255, 63); 179 this.labelOutlineVisible = true; 180 this.labelOutlinePaint = Color.BLACK; 181 this.labelOutlineStroke = new BasicStroke(0.5f); 182 this.pcs = new PropertyChangeSupport(this); 183 } 184 185 /** 186 * Returns the flag that indicates whether the crosshair is 187 * currently visible. 188 * 189 * @return A boolean. 190 * 191 * @see #setVisible(boolean) 192 */ 193 public boolean isVisible() { 194 return this.visible; 195 } 196 197 /** 198 * Sets the flag that controls the visibility of the crosshair and sends 199 * a proerty change event (with the name 'visible') to all registered 200 * listeners. 201 * 202 * @param visible the new flag value. 203 * 204 * @see #isVisible() 205 */ 206 public void setVisible(boolean visible) { 207 boolean old = this.visible; 208 this.visible = visible; 209 this.pcs.firePropertyChange("visible", old, visible); 210 } 211 212 /** 213 * Returns the crosshair value. 214 * 215 * @return The crosshair value. 216 * 217 * @see #setValue(double) 218 */ 219 public double getValue() { 220 return this.value; 221 } 222 223 /** 224 * Sets the crosshair value and sends a property change event with the name 225 * 'value' to all registered listeners. 226 * 227 * @param value the value. 228 * 229 * @see #getValue() 230 */ 231 public void setValue(double value) { 232 Double oldValue = this.value; 233 this.value = value; 234 this.pcs.firePropertyChange("value", oldValue, value); 235 } 236 237 /** 238 * Returns the paint for the crosshair line. 239 * 240 * @return The paint (never {@code null}). 241 * 242 * @see #setPaint(java.awt.Paint) 243 */ 244 public Paint getPaint() { 245 return this.paint; 246 } 247 248 /** 249 * Sets the paint for the crosshair line and sends a property change event 250 * with the name "paint" to all registered listeners. 251 * 252 * @param paint the paint ({@code null} not permitted). 253 * 254 * @see #getPaint() 255 */ 256 public void setPaint(Paint paint) { 257 Paint old = this.paint; 258 this.paint = paint; 259 this.pcs.firePropertyChange("paint", old, paint); 260 } 261 262 /** 263 * Returns the stroke for the crosshair line. 264 * 265 * @return The stroke (never {@code null}). 266 * 267 * @see #setStroke(java.awt.Stroke) 268 */ 269 public Stroke getStroke() { 270 return this.stroke; 271 } 272 273 /** 274 * Sets the stroke for the crosshair line and sends a property change event 275 * with the name "stroke" to all registered listeners. 276 * 277 * @param stroke the stroke ({@code null} not permitted). 278 * 279 * @see #getStroke() 280 */ 281 public void setStroke(Stroke stroke) { 282 Stroke old = this.stroke; 283 this.stroke = stroke; 284 this.pcs.firePropertyChange("stroke", old, stroke); 285 } 286 287 /** 288 * Returns the flag that controls whether a label is drawn for 289 * this crosshair. 290 * 291 * @return A boolean. 292 * 293 * @see #setLabelVisible(boolean) 294 */ 295 public boolean isLabelVisible() { 296 return this.labelVisible; 297 } 298 299 /** 300 * Sets the flag that controls whether a label is drawn for the 301 * crosshair and sends a property change event (with the name 302 * 'labelVisible') to all registered listeners. 303 * 304 * @param visible the new flag value. 305 * 306 * @see #isLabelVisible() 307 */ 308 public void setLabelVisible(boolean visible) { 309 boolean old = this.labelVisible; 310 this.labelVisible = visible; 311 this.pcs.firePropertyChange("labelVisible", old, visible); 312 } 313 314 /** 315 * Returns the crosshair label generator. 316 * 317 * @return The label crosshair generator (never {@code null}). 318 * 319 * @see #setLabelGenerator(org.jfree.chart.labels.CrosshairLabelGenerator) 320 */ 321 public CrosshairLabelGenerator getLabelGenerator() { 322 return this.labelGenerator; 323 } 324 325 /** 326 * Sets the crosshair label generator and sends a property change event 327 * (with the name 'labelGenerator') to all registered listeners. 328 * 329 * @param generator the new generator ({@code null} not permitted). 330 * 331 * @see #getLabelGenerator() 332 */ 333 public void setLabelGenerator(CrosshairLabelGenerator generator) { 334 Args.nullNotPermitted(generator, "generator"); 335 CrosshairLabelGenerator old = this.labelGenerator; 336 this.labelGenerator = generator; 337 this.pcs.firePropertyChange("labelGenerator", old, generator); 338 } 339 340 /** 341 * Returns the label anchor point. 342 * 343 * @return the label anchor point (never {@code null}). 344 * 345 * @see #setLabelAnchor(org.jfree.chart.ui.RectangleAnchor) 346 */ 347 public RectangleAnchor getLabelAnchor() { 348 return this.labelAnchor; 349 } 350 351 /** 352 * Sets the label anchor point and sends a property change event (with the 353 * name 'labelAnchor') to all registered listeners. 354 * 355 * @param anchor the anchor ({@code null} not permitted). 356 * 357 * @see #getLabelAnchor() 358 */ 359 public void setLabelAnchor(RectangleAnchor anchor) { 360 RectangleAnchor old = this.labelAnchor; 361 this.labelAnchor = anchor; 362 this.pcs.firePropertyChange("labelAnchor", old, anchor); 363 } 364 365 /** 366 * Returns the x-offset for the label (in Java2D units). 367 * 368 * @return The x-offset. 369 * 370 * @see #setLabelXOffset(double) 371 */ 372 public double getLabelXOffset() { 373 return this.labelXOffset; 374 } 375 376 /** 377 * Sets the x-offset and sends a property change event (with the name 378 * 'labelXOffset') to all registered listeners. 379 * 380 * @param offset the new offset. 381 * 382 * @see #getLabelXOffset() 383 */ 384 public void setLabelXOffset(double offset) { 385 Double old = this.labelXOffset; 386 this.labelXOffset = offset; 387 this.pcs.firePropertyChange("labelXOffset", old, offset); 388 } 389 390 /** 391 * Returns the y-offset for the label (in Java2D units). 392 * 393 * @return The y-offset. 394 * 395 * @see #setLabelYOffset(double) 396 */ 397 public double getLabelYOffset() { 398 return this.labelYOffset; 399 } 400 401 /** 402 * Sets the y-offset and sends a property change event (with the name 403 * 'labelYOffset') to all registered listeners. 404 * 405 * @param offset the new offset. 406 * 407 * @see #getLabelYOffset() 408 */ 409 public void setLabelYOffset(double offset) { 410 Double old = this.labelYOffset; 411 this.labelYOffset = offset; 412 this.pcs.firePropertyChange("labelYOffset", old, offset); 413 } 414 415 /** 416 * Returns the label padding. 417 * 418 * @return The label padding (never {@code null}). 419 * @since 1.5.6 420 */ 421 public RectangleInsets getLabelPadding() { 422 return labelPadding; 423 } 424 425 /** 426 * Sets the label padding and sends a property change event (with the name 427 * 'labelPadding') to all registered listeners. 428 * 429 * @param padding the padding ({@code null} not permitted). 430 * @since 1.5.6 431 */ 432 public void setLabelPadding(RectangleInsets padding) { 433 Args.nullNotPermitted(padding, "padding"); 434 RectangleInsets old = this.labelPadding; 435 this.labelPadding = padding; 436 this.pcs.firePropertyChange("labelPadding", old, padding); 437 } 438 439 /** 440 * Returns the label font. 441 * 442 * @return The label font (never {@code null}). 443 * 444 * @see #setLabelFont(java.awt.Font) 445 */ 446 public Font getLabelFont() { 447 return this.labelFont; 448 } 449 450 /** 451 * Sets the label font and sends a property change event (with the name 452 * 'labelFont') to all registered listeners. 453 * 454 * @param font the font ({@code null} not permitted). 455 * 456 * @see #getLabelFont() 457 */ 458 public void setLabelFont(Font font) { 459 Args.nullNotPermitted(font, "font"); 460 Font old = this.labelFont; 461 this.labelFont = font; 462 this.pcs.firePropertyChange("labelFont", old, font); 463 } 464 465 /** 466 * Returns the label paint. The default value is {@code Color.BLACK}. 467 * 468 * @return The label paint (never {@code null}). 469 * 470 * @see #setLabelPaint 471 */ 472 public Paint getLabelPaint() { 473 return this.labelPaint; 474 } 475 476 /** 477 * Sets the label paint and sends a property change event (with the name 478 * 'labelPaint') to all registered listeners. 479 * 480 * @param paint the paint ({@code null} not permitted). 481 * 482 * @see #getLabelPaint() 483 */ 484 public void setLabelPaint(Paint paint) { 485 Args.nullNotPermitted(paint, "paint"); 486 Paint old = this.labelPaint; 487 this.labelPaint = paint; 488 this.pcs.firePropertyChange("labelPaint", old, paint); 489 } 490 491 /** 492 * Returns the label background paint. 493 * 494 * @return The label background paint (possibly {@code null}). 495 * 496 * @see #setLabelBackgroundPaint(java.awt.Paint) 497 */ 498 public Paint getLabelBackgroundPaint() { 499 return this.labelBackgroundPaint; 500 } 501 502 /** 503 * Sets the label background paint and sends a property change event with 504 * the name 'labelBackgroundPaint') to all registered listeners. 505 * 506 * @param paint the paint ({@code null} permitted). 507 * 508 * @see #getLabelBackgroundPaint() 509 */ 510 public void setLabelBackgroundPaint(Paint paint) { 511 Paint old = this.labelBackgroundPaint; 512 this.labelBackgroundPaint = paint; 513 this.pcs.firePropertyChange("labelBackgroundPaint", old, paint); 514 } 515 516 /** 517 * Returns the flag that controls the visibility of the label outline. 518 * The default value is {@code true}. 519 * 520 * @return A boolean. 521 * 522 * @see #setLabelOutlineVisible(boolean) 523 */ 524 public boolean isLabelOutlineVisible() { 525 return this.labelOutlineVisible; 526 } 527 528 /** 529 * Sets the flag that controls the visibility of the label outlines and 530 * sends a property change event (with the name "labelOutlineVisible") to 531 * all registered listeners. 532 * 533 * @param visible the new flag value. 534 * 535 * @see #isLabelOutlineVisible() 536 */ 537 public void setLabelOutlineVisible(boolean visible) { 538 boolean old = this.labelOutlineVisible; 539 this.labelOutlineVisible = visible; 540 this.pcs.firePropertyChange("labelOutlineVisible", old, visible); 541 } 542 543 /** 544 * Returns the label outline paint. 545 * 546 * @return The label outline paint (never {@code null}). 547 * 548 * @see #setLabelOutlinePaint(java.awt.Paint) 549 */ 550 public Paint getLabelOutlinePaint() { 551 return this.labelOutlinePaint; 552 } 553 554 /** 555 * Sets the label outline paint and sends a property change event (with the 556 * name "labelOutlinePaint") to all registered listeners. 557 * 558 * @param paint the paint ({@code null} not permitted). 559 * 560 * @see #getLabelOutlinePaint() 561 */ 562 public void setLabelOutlinePaint(Paint paint) { 563 Args.nullNotPermitted(paint, "paint"); 564 Paint old = this.labelOutlinePaint; 565 this.labelOutlinePaint = paint; 566 this.pcs.firePropertyChange("labelOutlinePaint", old, paint); 567 } 568 569 /** 570 * Returns the label outline stroke. The default value is 571 * {@code BasicStroke(0.5)}. 572 * 573 * @return The label outline stroke (never {@code null}). 574 * 575 * @see #setLabelOutlineStroke(java.awt.Stroke) 576 */ 577 public Stroke getLabelOutlineStroke() { 578 return this.labelOutlineStroke; 579 } 580 581 /** 582 * Sets the label outline stroke and sends a property change event (with 583 * the name 'labelOutlineStroke') to all registered listeners. 584 * 585 * @param stroke the stroke ({@code null} not permitted). 586 * 587 * @see #getLabelOutlineStroke() 588 */ 589 public void setLabelOutlineStroke(Stroke stroke) { 590 Args.nullNotPermitted(stroke, "stroke"); 591 Stroke old = this.labelOutlineStroke; 592 this.labelOutlineStroke = stroke; 593 this.pcs.firePropertyChange("labelOutlineStroke", old, stroke); 594 } 595 596 /** 597 * Tests this crosshair for equality with an arbitrary object. 598 * 599 * @param obj the object ({@code null} permitted). 600 * 601 * @return A boolean. 602 */ 603 @Override 604 public boolean equals(Object obj) { 605 if (obj == this) { 606 return true; 607 } 608 if (!(obj instanceof Crosshair)) { 609 return false; 610 } 611 Crosshair that = (Crosshair) obj; 612 if (this.visible != that.visible) { 613 return false; 614 } 615 if (Double.compare(this.value, that.value) != 0) { 616 return false; 617 } 618 if (!PaintUtils.equal(this.paint, that.paint)) { 619 return false; 620 } 621 if (!this.stroke.equals(that.stroke)) { 622 return false; 623 } 624 if (this.labelVisible != that.labelVisible) { 625 return false; 626 } 627 if (!this.labelGenerator.equals(that.labelGenerator)) { 628 return false; 629 } 630 if (!this.labelAnchor.equals(that.labelAnchor)) { 631 return false; 632 } 633 if (Double.compare(this.labelXOffset, that.labelXOffset) != 0) { 634 return false; 635 } 636 if (Double.compare(this.labelYOffset, that.labelYOffset) != 0) { 637 return false; 638 } 639 if (!this.labelPadding.equals(that.labelPadding)) { 640 return false; 641 } 642 if (!this.labelFont.equals(that.labelFont)) { 643 return false; 644 } 645 if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) { 646 return false; 647 } 648 if (!PaintUtils.equal(this.labelBackgroundPaint, 649 that.labelBackgroundPaint)) { 650 return false; 651 } 652 if (this.labelOutlineVisible != that.labelOutlineVisible) { 653 return false; 654 } 655 if (!PaintUtils.equal(this.labelOutlinePaint, 656 that.labelOutlinePaint)) { 657 return false; 658 } 659 if (!this.labelOutlineStroke.equals(that.labelOutlineStroke)) { 660 return false; 661 } 662 return true; // can't find any difference 663 } 664 665 /** 666 * Returns a hash code for this instance. 667 * 668 * @return A hash code. 669 */ 670 @Override 671 public int hashCode() { 672 int hash = 7; 673 hash = HashUtils.hashCode(hash, this.visible); 674 hash = HashUtils.hashCode(hash, this.value); 675 hash = HashUtils.hashCode(hash, this.paint); 676 hash = HashUtils.hashCode(hash, this.stroke); 677 hash = HashUtils.hashCode(hash, this.labelVisible); 678 hash = HashUtils.hashCode(hash, this.labelAnchor); 679 hash = HashUtils.hashCode(hash, this.labelGenerator); 680 hash = HashUtils.hashCode(hash, this.labelXOffset); 681 hash = HashUtils.hashCode(hash, this.labelYOffset); 682 hash = HashUtils.hashCode(hash, this.labelPadding); 683 hash = HashUtils.hashCode(hash, this.labelFont); 684 hash = HashUtils.hashCode(hash, this.labelPaint); 685 hash = HashUtils.hashCode(hash, this.labelBackgroundPaint); 686 hash = HashUtils.hashCode(hash, this.labelOutlineVisible); 687 hash = HashUtils.hashCode(hash, this.labelOutlineStroke); 688 hash = HashUtils.hashCode(hash, this.labelOutlinePaint); 689 return hash; 690 } 691 692 /** 693 * Returns an independent copy of this instance. 694 * 695 * @return An independent copy of this instance. 696 * 697 * @throws java.lang.CloneNotSupportedException if there is a problem with 698 * cloning. 699 */ 700 @Override 701 public Object clone() throws CloneNotSupportedException { 702 // FIXME: clone generator 703 return super.clone(); 704 } 705 706 /** 707 * Adds a property change listener. 708 * 709 * @param l the listener. 710 * 711 * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) 712 */ 713 public void addPropertyChangeListener(PropertyChangeListener l) { 714 this.pcs.addPropertyChangeListener(l); 715 } 716 717 /** 718 * Removes a property change listener. 719 * 720 * @param l the listener. 721 * 722 * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) 723 */ 724 public void removePropertyChangeListener(PropertyChangeListener l) { 725 this.pcs.removePropertyChangeListener(l); 726 } 727 728 /** 729 * Provides serialization support. 730 * 731 * @param stream the output stream. 732 * 733 * @throws IOException if there is an I/O error. 734 */ 735 private void writeObject(ObjectOutputStream stream) throws IOException { 736 stream.defaultWriteObject(); 737 SerialUtils.writePaint(this.paint, stream); 738 SerialUtils.writeStroke(this.stroke, stream); 739 SerialUtils.writePaint(this.labelPaint, stream); 740 SerialUtils.writePaint(this.labelBackgroundPaint, stream); 741 SerialUtils.writeStroke(this.labelOutlineStroke, stream); 742 SerialUtils.writePaint(this.labelOutlinePaint, stream); 743 } 744 745 /** 746 * Provides serialization support. 747 * 748 * @param stream the input stream. 749 * 750 * @throws IOException if there is an I/O error. 751 * @throws ClassNotFoundException if there is a classpath problem. 752 */ 753 private void readObject(ObjectInputStream stream) 754 throws IOException, ClassNotFoundException { 755 stream.defaultReadObject(); 756 this.paint = SerialUtils.readPaint(stream); 757 this.stroke = SerialUtils.readStroke(stream); 758 this.labelPaint = SerialUtils.readPaint(stream); 759 this.labelBackgroundPaint = SerialUtils.readPaint(stream); 760 this.labelOutlineStroke = SerialUtils.readStroke(stream); 761 this.labelOutlinePaint = SerialUtils.readPaint(stream); 762 this.pcs = new PropertyChangeSupport(this); 763 } 764 765}