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