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 * Marker.java 029 * ----------- 030 * (C) Copyright 2002-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Nicolas Brodu; 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.io.IOException; 045import java.io.ObjectInputStream; 046import java.io.ObjectOutputStream; 047import java.io.Serializable; 048import java.util.EventListener; 049import java.util.Objects; 050 051import javax.swing.event.EventListenerList; 052 053import org.jfree.chart.event.MarkerChangeEvent; 054import org.jfree.chart.event.MarkerChangeListener; 055import org.jfree.chart.api.LengthAdjustmentType; 056import org.jfree.chart.api.RectangleAnchor; 057import org.jfree.chart.api.RectangleInsets; 058import org.jfree.chart.text.TextAnchor; 059import org.jfree.chart.internal.PaintUtils; 060import org.jfree.chart.internal.Args; 061import org.jfree.chart.internal.SerialUtils; 062 063/** 064 * The base class for markers that can be added to plots to highlight a value 065 * or range of values. 066 */ 067public abstract class Marker implements Cloneable, Serializable { 068 069 /** For serialization. */ 070 private static final long serialVersionUID = -734389651405327166L; 071 072 /** The paint (null is not allowed). */ 073 private transient Paint paint; 074 075 /** The stroke (null is not allowed). */ 076 private transient Stroke stroke; 077 078 /** The outline paint. */ 079 private transient Paint outlinePaint; 080 081 /** The outline stroke. */ 082 private transient Stroke outlineStroke; 083 084 /** The alpha transparency. */ 085 private float alpha; 086 087 /** The label. */ 088 private String label = null; 089 090 /** The label font. */ 091 private Font labelFont; 092 093 /** The label paint. */ 094 private transient Paint labelPaint; 095 096 /** The label background color. */ 097 private Color labelBackgroundColor; 098 099 /** The label position. */ 100 private RectangleAnchor labelAnchor; 101 102 /** The text anchor for the label. */ 103 private TextAnchor labelTextAnchor; 104 105 /** The label offset from the marker rectangle (see also labelOffsetType). */ 106 private RectangleInsets labelOffset; 107 108 /** The offset type for the label (see also labelOffset). */ 109 private LengthAdjustmentType labelOffsetType; 110 111 /** Storage for registered change listeners. */ 112 private transient EventListenerList listenerList; 113 114 /** 115 * Creates a new marker with default attributes. 116 */ 117 protected Marker() { 118 this(Color.GRAY); 119 } 120 121 /** 122 * Constructs a new marker. 123 * 124 * @param paint the paint ({@code null} not permitted). 125 */ 126 protected Marker(Paint paint) { 127 this(paint, new BasicStroke(0.5f), Color.GRAY, new BasicStroke(0.5f), 128 0.80f); 129 } 130 131 /** 132 * Constructs a new marker. 133 * 134 * @param paint the paint ({@code null} not permitted). 135 * @param stroke the stroke ({@code null} not permitted). 136 * @param outlinePaint the outline paint ({@code null} permitted). 137 * @param outlineStroke the outline stroke ({@code null} permitted). 138 * @param alpha the alpha transparency (must be in the range 0.0f to 139 * 1.0f). 140 * 141 * @throws IllegalArgumentException if {@code paint} or 142 * {@code stroke} is {@code null}, or {@code alpha} is 143 * not in the specified range. 144 */ 145 protected Marker(Paint paint, Stroke stroke, Paint outlinePaint, 146 Stroke outlineStroke, float alpha) { 147 148 Args.nullNotPermitted(paint, "paint"); 149 Args.nullNotPermitted(stroke, "stroke"); 150 if (alpha < 0.0f || alpha > 1.0f) { 151 throw new IllegalArgumentException( 152 "The 'alpha' value must be in the range 0.0f to 1.0f"); 153 } 154 155 this.paint = paint; 156 this.stroke = stroke; 157 this.outlinePaint = outlinePaint; 158 this.outlineStroke = outlineStroke; 159 this.alpha = alpha; 160 161 this.labelFont = new Font("SansSerif", Font.PLAIN, 9); 162 this.labelPaint = Color.BLACK; 163 this.labelBackgroundColor = new Color(100, 100, 100, 100); 164 this.labelAnchor = RectangleAnchor.TOP_LEFT; 165 this.labelOffset = new RectangleInsets(3.0, 3.0, 3.0, 3.0); 166 this.labelOffsetType = LengthAdjustmentType.CONTRACT; 167 this.labelTextAnchor = TextAnchor.CENTER; 168 169 this.listenerList = new EventListenerList(); 170 } 171 172 /** 173 * Returns the paint. 174 * 175 * @return The paint (never {@code null}). 176 * 177 * @see #setPaint(Paint) 178 */ 179 public Paint getPaint() { 180 return this.paint; 181 } 182 183 /** 184 * Sets the paint and sends a {@link MarkerChangeEvent} to all registered 185 * listeners. 186 * 187 * @param paint the paint ({@code null} not permitted). 188 * 189 * @see #getPaint() 190 */ 191 public void setPaint(Paint paint) { 192 Args.nullNotPermitted(paint, "paint"); 193 this.paint = paint; 194 notifyListeners(new MarkerChangeEvent(this)); 195 } 196 197 /** 198 * Returns the stroke. 199 * 200 * @return The stroke (never {@code null}). 201 * 202 * @see #setStroke(Stroke) 203 */ 204 public Stroke getStroke() { 205 return this.stroke; 206 } 207 208 /** 209 * Sets the stroke and sends a {@link MarkerChangeEvent} to all registered 210 * listeners. 211 * 212 * @param stroke the stroke ({@code null}not permitted). 213 * 214 * @see #getStroke() 215 */ 216 public void setStroke(Stroke stroke) { 217 Args.nullNotPermitted(stroke, "stroke"); 218 this.stroke = stroke; 219 notifyListeners(new MarkerChangeEvent(this)); 220 } 221 222 /** 223 * Returns the outline paint. 224 * 225 * @return The outline paint (possibly {@code null}). 226 * 227 * @see #setOutlinePaint(Paint) 228 */ 229 public Paint getOutlinePaint() { 230 return this.outlinePaint; 231 } 232 233 /** 234 * Sets the outline paint and sends a {@link MarkerChangeEvent} to all 235 * registered listeners. 236 * 237 * @param paint the paint ({@code null} permitted). 238 * 239 * @see #getOutlinePaint() 240 */ 241 public void setOutlinePaint(Paint paint) { 242 this.outlinePaint = paint; 243 notifyListeners(new MarkerChangeEvent(this)); 244 } 245 246 /** 247 * Returns the outline stroke. 248 * 249 * @return The outline stroke (possibly {@code null}). 250 * 251 * @see #setOutlineStroke(Stroke) 252 */ 253 public Stroke getOutlineStroke() { 254 return this.outlineStroke; 255 } 256 257 /** 258 * Sets the outline stroke and sends a {@link MarkerChangeEvent} to all 259 * registered listeners. 260 * 261 * @param stroke the stroke ({@code null} permitted). 262 * 263 * @see #getOutlineStroke() 264 */ 265 public void setOutlineStroke(Stroke stroke) { 266 this.outlineStroke = stroke; 267 notifyListeners(new MarkerChangeEvent(this)); 268 } 269 270 /** 271 * Returns the alpha transparency. 272 * 273 * @return The alpha transparency. 274 * 275 * @see #setAlpha(float) 276 */ 277 public float getAlpha() { 278 return this.alpha; 279 } 280 281 /** 282 * Sets the alpha transparency that should be used when drawing the 283 * marker, and sends a {@link MarkerChangeEvent} to all registered 284 * listeners. The alpha transparency is a value in the range 0.0f 285 * (completely transparent) to 1.0f (completely opaque). 286 * 287 * @param alpha the alpha transparency (must be in the range 0.0f to 288 * 1.0f). 289 * 290 * @throws IllegalArgumentException if {@code alpha} is not in the 291 * specified range. 292 * 293 * @see #getAlpha() 294 */ 295 public void setAlpha(float alpha) { 296 if (alpha < 0.0f || alpha > 1.0f) { 297 throw new IllegalArgumentException( 298 "The 'alpha' value must be in the range 0.0f to 1.0f"); 299 } 300 this.alpha = alpha; 301 notifyListeners(new MarkerChangeEvent(this)); 302 } 303 304 /** 305 * Returns the label (if {@code null} no label is displayed). 306 * 307 * @return The label (possibly {@code null}). 308 * 309 * @see #setLabel(String) 310 */ 311 public String getLabel() { 312 return this.label; 313 } 314 315 /** 316 * Sets the label (if {@code null} no label is displayed) and sends a 317 * {@link MarkerChangeEvent} to all registered listeners. 318 * 319 * @param label the label ({@code null} permitted). 320 * 321 * @see #getLabel() 322 */ 323 public void setLabel(String label) { 324 this.label = label; 325 notifyListeners(new MarkerChangeEvent(this)); 326 } 327 328 /** 329 * Returns the label font. 330 * 331 * @return The label font (never {@code null}). 332 * 333 * @see #setLabelFont(Font) 334 */ 335 public Font getLabelFont() { 336 return this.labelFont; 337 } 338 339 /** 340 * Sets the label font and sends a {@link MarkerChangeEvent} to all 341 * registered listeners. 342 * 343 * @param font the font ({@code null} not permitted). 344 * 345 * @see #getLabelFont() 346 */ 347 public void setLabelFont(Font font) { 348 Args.nullNotPermitted(font, "font"); 349 this.labelFont = font; 350 notifyListeners(new MarkerChangeEvent(this)); 351 } 352 353 /** 354 * Returns the label paint. 355 * 356 * @return The label paint (never {@code null}). 357 * 358 * @see #setLabelPaint(Paint) 359 */ 360 public Paint getLabelPaint() { 361 return this.labelPaint; 362 } 363 364 /** 365 * Sets the label paint and sends a {@link MarkerChangeEvent} to all 366 * registered listeners. 367 * 368 * @param paint the paint ({@code null} not permitted). 369 * 370 * @see #getLabelPaint() 371 */ 372 public void setLabelPaint(Paint paint) { 373 Args.nullNotPermitted(paint, "paint"); 374 this.labelPaint = paint; 375 notifyListeners(new MarkerChangeEvent(this)); 376 } 377 378 /** 379 * Returns the label background color. The default value is 380 * {@code Color(100, 100, 100, 100)}.. 381 * 382 * @return The label background color (never {@code null}). 383 */ 384 public Color getLabelBackgroundColor() { 385 return this.labelBackgroundColor; 386 } 387 388 /** 389 * Sets the label background color. 390 * 391 * @param color the color ({@code null} not permitted). 392 */ 393 public void setLabelBackgroundColor(Color color) { 394 Args.nullNotPermitted(color, "color"); 395 this.labelBackgroundColor = color; 396 } 397 398 /** 399 * Returns the label anchor. This defines the position of the label 400 * anchor, relative to the bounds of the marker. 401 * 402 * @return The label anchor (never {@code null}). 403 * 404 * @see #setLabelAnchor(RectangleAnchor) 405 */ 406 public RectangleAnchor getLabelAnchor() { 407 return this.labelAnchor; 408 } 409 410 /** 411 * Sets the label anchor and sends a {@link MarkerChangeEvent} to all 412 * registered listeners. The anchor defines the position of the label 413 * anchor, relative to the bounds of the marker. 414 * 415 * @param anchor the anchor ({@code null} not permitted). 416 * 417 * @see #getLabelAnchor() 418 */ 419 public void setLabelAnchor(RectangleAnchor anchor) { 420 Args.nullNotPermitted(anchor, "anchor"); 421 this.labelAnchor = anchor; 422 notifyListeners(new MarkerChangeEvent(this)); 423 } 424 425 /** 426 * Returns the label offset. 427 * 428 * @return The label offset (never {@code null}). 429 * 430 * @see #setLabelOffset(RectangleInsets) 431 */ 432 public RectangleInsets getLabelOffset() { 433 return this.labelOffset; 434 } 435 436 /** 437 * Sets the label offset and sends a {@link MarkerChangeEvent} to all 438 * registered listeners. 439 * 440 * @param offset the label offset ({@code null} not permitted). 441 * 442 * @see #getLabelOffset() 443 */ 444 public void setLabelOffset(RectangleInsets offset) { 445 Args.nullNotPermitted(offset, "offset"); 446 this.labelOffset = offset; 447 notifyListeners(new MarkerChangeEvent(this)); 448 } 449 450 /** 451 * Returns the label offset type. 452 * 453 * @return The type (never {@code null}). 454 * 455 * @see #setLabelOffsetType(LengthAdjustmentType) 456 */ 457 public LengthAdjustmentType getLabelOffsetType() { 458 return this.labelOffsetType; 459 } 460 461 /** 462 * Sets the label offset type and sends a {@link MarkerChangeEvent} to all 463 * registered listeners. 464 * 465 * @param adj the type ({@code null} not permitted). 466 * 467 * @see #getLabelOffsetType() 468 */ 469 public void setLabelOffsetType(LengthAdjustmentType adj) { 470 Args.nullNotPermitted(adj, "adj"); 471 this.labelOffsetType = adj; 472 notifyListeners(new MarkerChangeEvent(this)); 473 } 474 475 /** 476 * Returns the label text anchor. 477 * 478 * @return The label text anchor (never {@code null}). 479 * 480 * @see #setLabelTextAnchor(TextAnchor) 481 */ 482 public TextAnchor getLabelTextAnchor() { 483 return this.labelTextAnchor; 484 } 485 486 /** 487 * Sets the label text anchor and sends a {@link MarkerChangeEvent} to 488 * all registered listeners. 489 * 490 * @param anchor the label text anchor ({@code null} not permitted). 491 * 492 * @see #getLabelTextAnchor() 493 */ 494 public void setLabelTextAnchor(TextAnchor anchor) { 495 Args.nullNotPermitted(anchor, "anchor"); 496 this.labelTextAnchor = anchor; 497 notifyListeners(new MarkerChangeEvent(this)); 498 } 499 500 /** 501 * Registers an object for notification of changes to the marker. 502 * 503 * @param listener the object to be registered. 504 * 505 * @see #removeChangeListener(MarkerChangeListener) 506 */ 507 public void addChangeListener(MarkerChangeListener listener) { 508 this.listenerList.add(MarkerChangeListener.class, listener); 509 } 510 511 /** 512 * Unregisters an object for notification of changes to the marker. 513 * 514 * @param listener the object to be unregistered. 515 * 516 * @see #addChangeListener(MarkerChangeListener) 517 */ 518 public void removeChangeListener(MarkerChangeListener listener) { 519 this.listenerList.remove(MarkerChangeListener.class, listener); 520 } 521 522 /** 523 * Notifies all registered listeners that the marker has been modified. 524 * 525 * @param event information about the change event. 526 */ 527 public void notifyListeners(MarkerChangeEvent event) { 528 529 Object[] listeners = this.listenerList.getListenerList(); 530 for (int i = listeners.length - 2; i >= 0; i -= 2) { 531 if (listeners[i] == MarkerChangeListener.class) { 532 ((MarkerChangeListener) listeners[i + 1]).markerChanged(event); 533 } 534 } 535 536 } 537 538 /** 539 * Returns an array containing all the listeners of the specified type. 540 * 541 * @param listenerType the listener type. 542 * 543 * @return The array of listeners. 544 */ 545 public <T extends EventListener> T[] getListeners(Class<T> listenerType) { 546 return this.listenerList.getListeners(listenerType); 547 } 548 549 /** 550 * Tests the marker for equality with an arbitrary object. 551 * 552 * @param obj the object ({@code null} permitted). 553 * 554 * @return A boolean. 555 */ 556 @Override 557 public boolean equals(Object obj) { 558 if (obj == this) { 559 return true; 560 } 561 if (!(obj instanceof Marker)) { 562 return false; 563 } 564 Marker that = (Marker) obj; 565 if (!PaintUtils.equal(this.paint, that.paint)) { 566 return false; 567 } 568 if (!Objects.equals(this.stroke, that.stroke)) { 569 return false; 570 } 571 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 572 return false; 573 } 574 if (!Objects.equals(this.outlineStroke, that.outlineStroke)) { 575 return false; 576 } 577 if (this.alpha != that.alpha) { 578 return false; 579 } 580 if (!Objects.equals(this.label, that.label)) { 581 return false; 582 } 583 if (!Objects.equals(this.labelFont, that.labelFont)) { 584 return false; 585 } 586 if (!PaintUtils.equal(this.labelPaint, that.labelPaint)) { 587 return false; 588 } 589 if (!this.labelBackgroundColor.equals(that.labelBackgroundColor)) { 590 return false; 591 } 592 if (this.labelAnchor != that.labelAnchor) { 593 return false; 594 } 595 if (this.labelTextAnchor != that.labelTextAnchor) { 596 return false; 597 } 598 if (!Objects.equals(this.labelOffset, that.labelOffset)) { 599 return false; 600 } 601 if (!this.labelOffsetType.equals(that.labelOffsetType)) { 602 return false; 603 } 604 return true; 605 } 606 607 /** 608 * Returns a hash code for this instance. 609 * 610 * @return A hash code. 611 */ 612 @Override 613 public int hashCode() { 614 int hash = 7; 615 hash = 29 * hash + Objects.hashCode(this.label); 616 hash = 29 * hash + Objects.hashCode(this.labelAnchor); 617 hash = 29 * hash + Objects.hashCode(this.labelTextAnchor); 618 return hash; 619 } 620 621 /** 622 * Creates a clone of the marker. 623 * 624 * @return A clone. 625 * 626 * @throws CloneNotSupportedException never. 627 */ 628 @Override 629 public Object clone() throws CloneNotSupportedException { 630 return super.clone(); 631 } 632 633 /** 634 * Provides serialization support. 635 * 636 * @param stream the output stream. 637 * 638 * @throws IOException if there is an I/O error. 639 */ 640 private void writeObject(ObjectOutputStream stream) throws IOException { 641 stream.defaultWriteObject(); 642 SerialUtils.writePaint(this.paint, stream); 643 SerialUtils.writeStroke(this.stroke, stream); 644 SerialUtils.writePaint(this.outlinePaint, stream); 645 SerialUtils.writeStroke(this.outlineStroke, stream); 646 SerialUtils.writePaint(this.labelPaint, stream); 647 } 648 649 /** 650 * Provides serialization support. 651 * 652 * @param stream the input stream. 653 * 654 * @throws IOException if there is an I/O error. 655 * @throws ClassNotFoundException if there is a classpath problem. 656 */ 657 private void readObject(ObjectInputStream stream) 658 throws IOException, ClassNotFoundException { 659 stream.defaultReadObject(); 660 this.paint = SerialUtils.readPaint(stream); 661 this.stroke = SerialUtils.readStroke(stream); 662 this.outlinePaint = SerialUtils.readPaint(stream); 663 this.outlineStroke = SerialUtils.readStroke(stream); 664 this.labelPaint = SerialUtils.readPaint(stream); 665 this.listenerList = new EventListenerList(); 666 } 667 668}