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