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