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