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}