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