    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 * Crosshair.java
    029 * --------------
    030 * (C) Copyright 2009, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   -;
    034 *
    035 * Changes:
    036 * --------
    037 * 13-Feb-2009 : Version 1 (DG);
    038 *
    039 */
    040
    041package org.jfree.chart.plot;
    042
    043import java.awt.BasicStroke;
    044import java.awt.Color;
    045import java.awt.Font;
    046import java.awt.Paint;
    047import java.awt.Stroke;
    048import java.beans.PropertyChangeListener;
    049import java.beans.PropertyChangeSupport;
    050import java.io.IOException;
    051import java.io.ObjectInputStream;
    052import java.io.ObjectOutputStream;
    053import java.io.Serializable;
    054import org.jfree.chart.HashUtilities;
    055import org.jfree.chart.labels.CrosshairLabelGenerator;
    056import org.jfree.chart.labels.StandardCrosshairLabelGenerator;
    057import org.jfree.io.SerialUtilities;
    058import org.jfree.ui.RectangleAnchor;
    059import org.jfree.util.PaintUtilities;
    060import org.jfree.util.PublicCloneable;
    061
    062/**
    063 * A crosshair for display on a plot.
    064 *
    065 * @since 1.0.13
    066 */
    067public class Crosshair implements Cloneable, PublicCloneable, Serializable {
    068
    069    /** Flag controlling visibility. */
    070    private boolean visible;
    071
    072    /** The crosshair value. */
    073    private double value;
    074
    075    /** The paint for the crosshair line. */
    076    private transient Paint paint;
    077
    078    /** The stroke for the crosshair line. */
    079    private transient Stroke stroke;
    080
    081    /**
    082     * A flag that controls whether or not the crosshair has a label
    083     * visible.
    084     */
    085    private boolean labelVisible;
    086
    087    /**
    088     * The label anchor.
    089     */
    090    private RectangleAnchor labelAnchor;
    091
    092    /** A label generator. */
    093    private CrosshairLabelGenerator labelGenerator;
    094
    095    /**
    096     * The x-offset in Java2D units.
    097     */
    098    private double labelXOffset;
    099
    100    /**
    101     * The y-offset in Java2D units.
    102     */
    103    private double labelYOffset;
    104
    105    /**
    106     * The label font.
    107     */
    108    private Font labelFont;
    109
    110    /**
    111     * The label paint.
    112     */
    113    private transient Paint labelPaint;
    114
    115    /**
    116     * The label background paint.
    117     */
    118    private transient Paint labelBackgroundPaint;
    119
    120    /** A flag that controls the visibility of the label outline. */
    121    private boolean labelOutlineVisible;
    122
    123    /** The label outline stroke. */
    124    private transient Stroke labelOutlineStroke;
    125
    126    /** The label outline paint. */
    127    private transient Paint labelOutlinePaint;
    128
    129    /** Property change support. */
    130    private transient PropertyChangeSupport pcs;
    131
    132    /**
    133     * Creates a new crosshair with value 0.0.
    134     */
    135    public Crosshair() {
    136        this(0.0);
    137    }
    138
    139    /**
    140     * Creates a new crosshair with the specified value.
    141     *
    142     * @param value  the value.
    143     */
    144    public Crosshair(double value) {
    145       this(value, Color.black, new BasicStroke(1.0f));
    146    }
    147
    148    /**
    149     * Creates a new crosshair value with the specified value and line style.
    150     *
    151     * @param value  the value.
    152     * @param paint  the line paint (<code>null</code> not permitted).
    153     * @param stroke  the line stroke (<code>null</code> not permitted).
    154     */
    155    public Crosshair(double value, Paint paint, Stroke stroke) {
    156        if (paint == null) {
    157            throw new IllegalArgumentException("Null 'paint' argument.");
    158        }
    159        if (stroke == null) {
    160            throw new IllegalArgumentException("Null 'stroke' argument.");
    161        }
    162        this.visible = true;
    163        this.value = value;
    164        this.paint = paint;
    165        this.stroke = stroke;
    166        this.labelVisible = false;
    167        this.labelGenerator = new StandardCrosshairLabelGenerator();
    168        this.labelAnchor = RectangleAnchor.BOTTOM_LEFT;
    169        this.labelXOffset = 3.0;
    170        this.labelYOffset = 3.0;
    171        this.labelFont = new Font("Tahoma", Font.PLAIN, 12);
    172        this.labelPaint = Color.black;
    173        this.labelBackgroundPaint = new Color(0, 0, 255, 63);
    174        this.labelOutlineVisible = true;
    175        this.labelOutlinePaint = Color.black;
    176        this.labelOutlineStroke = new BasicStroke(0.5f);
    177        this.pcs = new PropertyChangeSupport(this);
    178    }
    179
    180    /**
    181     * Returns the flag that indicates whether or not the crosshair is
    182     * currently visible.
    183     *
    184     * @return A boolean.
    185     *
    186     * @see #setVisible(boolean)
    187     */
    188    public boolean isVisible() {
    189        return this.visible;
    190    }
    191
    192    /**
    193     * Sets the flag that controls the visibility of the crosshair and sends
    194     * a proerty change event (with the name 'visible') to all registered
    195     * listeners.
    196     *
    197     * @param visible  the new flag value.
    198     *
    199     * @see #isVisible()
    200     */
    201    public void setVisible(boolean visible) {
    202        boolean old = this.visible;
    203        this.visible = visible;
    204        this.pcs.firePropertyChange("visible", old, visible);
    205    }
    206
    207    /**
    208     * Returns the crosshair value.
    209     *
    210     * @return The crosshair value.
    211     *
    212     * @see #setValue(double)
    213     */
    214    public double getValue() {
    215        return this.value;
    216    }
    217
    218    /**
    219     * Sets the crosshair value and sends a property change event with the name
    220     * 'value' to all registered listeners.
    221     *
    222     * @param value  the value.
    223     *
    224     * @see #getValue()
    225     */
    226    public void setValue(double value) {
    227        Double oldValue = new Double(this.value);
    228        this.value = value;
    229        this.pcs.firePropertyChange("value", oldValue, new Double(value));
    230    }
    231
    232    /**
    233     * Returns the paint for the crosshair line.
    234     *
    235     * @return The paint (never <code>null</code>).
    236     *
    237     * @see #setPaint(java.awt.Paint)
    238     */
    239    public Paint getPaint() {
    240        return this.paint;
    241    }
    242
    243    /**
    244     * Sets the paint for the crosshair line and sends a property change event
    245     * with the name "paint" to all registered listeners.
    246     *
    247     * @param paint  the paint (<code>null</code> not permitted).
    248     *
    249     * @see #getPaint()
    250     */
    251    public void setPaint(Paint paint) {
    252        Paint old = this.paint;
    253        this.paint = paint;
    254        this.pcs.firePropertyChange("paint", old, paint);
    255    }
    256
    257    /**
    258     * Returns the stroke for the crosshair line.
    259     *
    260     * @return The stroke (never <code>null</code>).
    261     *
    262     * @see #setStroke(java.awt.Stroke)
    263     */
    264    public Stroke getStroke() {
    265        return this.stroke;
    266    }
    267
    268    /**
    269     * Sets the stroke for the crosshair line and sends a property change event
    270     * with the name "stroke" to all registered listeners.
    271     *
    272     * @param stroke  the stroke (<code>null</code> not permitted).
    273     *
    274     * @see #getStroke()
    275     */
    276    public void setStroke(Stroke stroke) {
    277        Stroke old = this.stroke;
    278        this.stroke = stroke;
    279        this.pcs.firePropertyChange("stroke", old, stroke);
    280    }
    281
    282    /**
    283     * Returns the flag that controls whether or not a label is drawn for
    284     * this crosshair.
    285     *
    286     * @return A boolean.
    287     *
    288     * @see #setLabelVisible(boolean)
    289     */
    290    public boolean isLabelVisible() {
    291        return this.labelVisible;
    292    }
    293
    294    /**
    295     * Sets the flag that controls whether or not a label is drawn for the
    296     * crosshair and sends a property change event (with the name
    297     * 'labelVisible') to all registered listeners.
    298     *
    299     * @param visible  the new flag value.
    300     *
    301     * @see #isLabelVisible()
    302     */
    303    public void setLabelVisible(boolean visible) {
    304        boolean old = this.labelVisible;
    305        this.labelVisible = visible;
    306        this.pcs.firePropertyChange("labelVisible", old, visible);
    307    }
    308
    309    /**
    310     * Returns the crosshair label generator.
    311     *
    312     * @return The label crosshair generator (never <code>null</code>).
    313     *
    314     * @see #setLabelGenerator(org.jfree.chart.labels.CrosshairLabelGenerator)
    315     */
    316    public CrosshairLabelGenerator getLabelGenerator() {
    317        return this.labelGenerator;
    318    }
    319
    320    /**
    321     * Sets the crosshair label generator and sends a property change event
    322     * (with the name 'labelGenerator') to all registered listeners.
    323     *
    324     * @param generator  the new generator (<code>null</code> not permitted).
    325     *
    326     * @see #getLabelGenerator()
    327     */
    328    public void setLabelGenerator(CrosshairLabelGenerator generator) {
    329        if (generator == null) {
    330            throw new IllegalArgumentException("Null 'generator' argument.");
    331        }
    332        CrosshairLabelGenerator old = this.labelGenerator;
    333        this.labelGenerator = generator;
    334        this.pcs.firePropertyChange("labelGenerator", old, generator);
    335    }
    336
    337    /**
    338     * Returns the label anchor point.
    339     *
    340     * @return the label anchor point (never <code>null</code>.
    341     *
    342     * @see #setLabelAnchor(org.jfree.ui.RectangleAnchor)
    343     */
    344    public RectangleAnchor getLabelAnchor() {
    345        return this.labelAnchor;
    346    }
    347
    348    /**
    349     * Sets the label anchor point and sends a property change event (with the
    350     * name 'labelAnchor') to all registered listeners.
    351     *
    352     * @param anchor  the anchor (<code>null</code> not permitted).
    353     *
    354     * @see #getLabelAnchor()
    355     */
    356    public void setLabelAnchor(RectangleAnchor anchor) {
    357        RectangleAnchor old = this.labelAnchor;
    358        this.labelAnchor = anchor;
    359        this.pcs.firePropertyChange("labelAnchor", old, anchor);
    360    }
    361
    362    /**
    363     * Returns the x-offset for the label (in Java2D units).
    364     *
    365     * @return The x-offset.
    366     *
    367     * @see #setLabelXOffset(double)
    368     */
    369    public double getLabelXOffset() {
    370        return this.labelXOffset;
    371    }
    372
    373    /**
    374     * Sets the x-offset and sends a property change event (with the name
    375     * 'labelXOffset') to all registered listeners.
    376     *
    377     * @param offset  the new offset.
    378     *
    379     * @see #getLabelXOffset()
    380     */
    381    public void setLabelXOffset(double offset) {
    382        Double old = new Double(this.labelXOffset);
    383        this.labelXOffset = offset;
    384        this.pcs.firePropertyChange("labelXOffset", old, new Double(offset));
    385    }
    386
    387    /**
    388     * Returns the y-offset for the label (in Java2D units).
    389     *
    390     * @return The y-offset.
    391     *
    392     * @see #setLabelYOffset(double)
    393     */
    394    public double getLabelYOffset() {
    395        return this.labelYOffset;
    396    }
    397
    398    /**
    399     * Sets the y-offset and sends a property change event (with the name
    400     * 'labelYOffset') to all registered listeners.
    401     *
    402     * @param offset  the new offset.
    403     *
    404     * @see #getLabelYOffset()
    405     */
    406    public void setLabelYOffset(double offset) {
    407        Double old = new Double(this.labelYOffset);
    408        this.labelYOffset = offset;
    409        this.pcs.firePropertyChange("labelYOffset", old, new Double(offset));
    410    }
    411
    412    /**
    413     * Returns the label font.
    414     *
    415     * @return The label font (never <code>null</code>).
    416     *
    417     * @see #setLabelFont(java.awt.Font)
    418     */
    419    public Font getLabelFont() {
    420        return this.labelFont;
    421    }
    422
    423    /**
    424     * Sets the label font and sends a property change event (with the name
    425     * 'labelFont') to all registered listeners.
    426     *
    427     * @param font  the font (<code>null</code> not permitted).
    428     *
    429     * @see #getLabelFont()
    430     */
    431    public void setLabelFont(Font font) {
    432        if (font == null) {
    433            throw new IllegalArgumentException("Null 'font' argument.");
    434        }
    435        Font old = this.labelFont;
    436        this.labelFont = font;
    437        this.pcs.firePropertyChange("labelFont", old, font);
    438    }
    439
    440    /**
    441     * Returns the label paint.
    442     *
    443     * @return The label paint (never <code>null</code>).
    444     *
    445     * @see #setLabelPaint
    446     */
    447    public Paint getLabelPaint() {
    448        return this.labelPaint;
    449    }
    450
    451    /**
    452     * Sets the label paint and sends a property change event (with the name
    453     * 'labelPaint') to all registered listeners.
    454     *
    455     * @param paint  the paint (<code>null</code> not permitted).
    456     *
    457     * @see #getLabelPaint()
    458     */
    459    public void setLabelPaint(Paint paint) {
    460        if (paint == null) {
    461            throw new IllegalArgumentException("Null 'paint' argument.");
    462        }
    463        Paint old = this.labelPaint;
    464        this.labelPaint = paint;
    465        this.pcs.firePropertyChange("labelPaint", old, paint);
    466    }
    467
    468    /**
    469     * Returns the label background paint.
    470     *
    471     * @return The label background paint (possibly <code>null</code>).
    472     *
    473     * @see #setLabelBackgroundPaint(java.awt.Paint)
    474     */
    475    public Paint getLabelBackgroundPaint() {
    476        return this.labelBackgroundPaint;
    477    }
    478
    479    /**
    480     * Sets the label background paint and sends a property change event with
    481     * the name 'labelBackgroundPaint') to all registered listeners.
    482     *
    483     * @param paint  the paint (<code>null</code> permitted).
    484     *
    485     * @see #getLabelBackgroundPaint()
    486     */
    487    public void setLabelBackgroundPaint(Paint paint) {
    488        Paint old = this.labelBackgroundPaint;
    489        this.labelBackgroundPaint = paint;
    490        this.pcs.firePropertyChange("labelBackgroundPaint", old, paint);
    491    }
    492
    493    /**
    494     * Returns the flag that controls the visibility of the label outline.
    495     *
    496     * @return A boolean.
    497     *
    498     * @see #setLabelOutlineVisible(boolean)
    499     */
    500    public boolean isLabelOutlineVisible() {
    501        return this.labelOutlineVisible;
    502    }
    503
    504    /**
    505     * Sets the flag that controls the visibility of the label outlines and
    506     * sends a property change event (with the name "labelOutlineVisible") to
    507     * all registered listeners.
    508     *
    509     * @param visible  the new flag value.
    510     *
    511     * @see #isLabelOutlineVisible()
    512     */
    513    public void setLabelOutlineVisible(boolean visible) {
    514        boolean old = this.labelOutlineVisible;
    515        this.labelOutlineVisible = visible;
    516        this.pcs.firePropertyChange("labelOutlineVisible", old, visible);
    517    }
    518
    519    /**
    520     * Returns the label outline paint.
    521     *
    522     * @return The label outline paint (never <code>null</code>).
    523     *
    524     * @see #setLabelOutlinePaint(java.awt.Paint)
    525     */
    526    public Paint getLabelOutlinePaint() {
    527        return this.labelOutlinePaint;
    528    }
    529
    530    /**
    531     * Sets the label outline paint and sends a property change event (with the
    532     * name "labelOutlinePaint") to all registered listeners.
    533     *
    534     * @param paint  the paint (<code>null</code> not permitted).
    535     *
    536     * @see #getLabelOutlinePaint()
    537     */
    538    public void setLabelOutlinePaint(Paint paint) {
    539        if (paint == null) {
    540            throw new IllegalArgumentException("Null 'paint' argument.");
    541        }
    542        Paint old = this.labelOutlinePaint;
    543        this.labelOutlinePaint = paint;
    544        this.pcs.firePropertyChange("labelOutlinePaint", old, paint);
    545    }
    546
    547    /**
    548     * Returns the label outline stroke.
    549     *
    550     * @return The label outline stroke (never <code>null</code>).
    551     *
    552     * @see #setLabelOutlineStroke(java.awt.Stroke)
    553     */
    554    public Stroke getLabelOutlineStroke() {
    555        return this.labelOutlineStroke;
    556    }
    557
    558    /**
    559     * Sets the label outline stroke and sends a property change event (with
    560     * the name 'labelOutlineStroke') to all registered listeners.
    561     *
    562     * @param stroke  the stroke (<code>null</code> not permitted).
    563     *
    564     * @see #getLabelOutlineStroke()
    565     */
    566    public void setLabelOutlineStroke(Stroke stroke) {
    567        if (stroke == null) {
    568            throw new IllegalArgumentException("Null 'stroke' argument.");
    569        }
    570        Stroke old = this.labelOutlineStroke;
    571        this.labelOutlineStroke = stroke;
    572        this.pcs.firePropertyChange("labelOutlineStroke", old, stroke);
    573    }
    574
    575    /**
    576     * Tests this crosshair for equality with an arbitrary object.
    577     *
    578     * @param obj  the object (<code>null</code> permitted).
    579     *
    580     * @return A boolean.
    581     */
    582    public boolean equals(Object obj) {
    583        if (obj == this) {
    584            return true;
    585        }
    586        if (!(obj instanceof Crosshair)) {
    587            return false;
    588        }
    589        Crosshair that = (Crosshair) obj;
    590        if (this.visible != that.visible) {
    591            return false;
    592        }
    593        if (this.value != that.value) {
    594            return false;
    595        }
    596        if (!PaintUtilities.equal(this.paint, that.paint)) {
    597            return false;
    598        }
    599        if (!this.stroke.equals(that.stroke)) {
    600            return false;
    601        }
    602        if (this.labelVisible != that.labelVisible) {
    603            return false;
    604        }
    605        if (!this.labelGenerator.equals(that.labelGenerator)) {
    606            return false;
    607        }
    608        if (!this.labelAnchor.equals(that.labelAnchor)) {
    609            return false;
    610        }
    611        if (this.labelXOffset != that.labelXOffset) {
    612            return false;
    613        }
    614        if (this.labelYOffset != that.labelYOffset) {
    615            return false;
    616        }
    617        if (!this.labelFont.equals(that.labelFont)) {
    618            return false;
    619        }
    620        if (!PaintUtilities.equal(this.labelPaint, that.labelPaint)) {
    621            return false;
    622        }
    623        if (!PaintUtilities.equal(this.labelBackgroundPaint,
    624                that.labelBackgroundPaint)) {
    625            return false;
    626        }
    627        if (this.labelOutlineVisible != that.labelOutlineVisible) {
    628            return false;
    629        }
    630        if (!PaintUtilities.equal(this.labelOutlinePaint,
    631                that.labelOutlinePaint)) {
    632            return false;
    633        }
    634        if (!this.labelOutlineStroke.equals(that.labelOutlineStroke)) {
    635            return false;
    636        }
    637        return true;  // can't find any difference
    638    }
    639
    640    /**
    641     * Returns a hash code for this instance.
    642     *
    643     * @return A hash code.
    644     */
    645    public int hashCode() {
    646        int hash = 7;
    647        hash = HashUtilities.hashCode(hash, this.visible);
    648        hash = HashUtilities.hashCode(hash, this.value);
    649        hash = HashUtilities.hashCode(hash, this.paint);
    650        hash = HashUtilities.hashCode(hash, this.stroke);
    651        hash = HashUtilities.hashCode(hash, this.labelVisible);
    652        hash = HashUtilities.hashCode(hash, this.labelAnchor);
    653        hash = HashUtilities.hashCode(hash, this.labelGenerator);
    654        hash = HashUtilities.hashCode(hash, this.labelXOffset);
    655        hash = HashUtilities.hashCode(hash, this.labelYOffset);
    656        hash = HashUtilities.hashCode(hash, this.labelFont);
    657        hash = HashUtilities.hashCode(hash, this.labelPaint);
    658        hash = HashUtilities.hashCode(hash, this.labelBackgroundPaint);
    659        hash = HashUtilities.hashCode(hash, this.labelOutlineVisible);
    660        hash = HashUtilities.hashCode(hash, this.labelOutlineStroke);
    661        hash = HashUtilities.hashCode(hash, this.labelOutlinePaint);
    662        return hash;
    663    }
    664
    665    /**
    666     * Returns an independent copy of this instance.
    667     *
    668     * @return An independent copy of this instance.
    669     *
    670     * @throws java.lang.CloneNotSupportedException
    671     */
    672    public Object clone() throws CloneNotSupportedException {
    673        // FIXME: clone generator
    674        return super.clone();
    675    }
    676
    677    /**
    678     * Adds a property change listener.
    679     *
    680     * @param l  the listener.
    681     *
    682     * @see #removePropertyChangeListener(java.beans.PropertyChangeListener)
    683     */
    684    public void addPropertyChangeListener(PropertyChangeListener l) {
    685        this.pcs.addPropertyChangeListener(l);
    686    }
    687
    688    /**
    689     * Removes a property change listener.
    690     *
    691     * @param l  the listener.
    692     *
    693     * @see #addPropertyChangeListener(java.beans.PropertyChangeListener)
    694     */
    695    public void removePropertyChangeListener(PropertyChangeListener l) {
    696        this.pcs.removePropertyChangeListener(l);
    697    }
    698
    699    /**
    700     * Provides serialization support.
    701     *
    702     * @param stream  the output stream.
    703     *
    704     * @throws IOException  if there is an I/O error.
    705     */
    706    private void writeObject(ObjectOutputStream stream) throws IOException {
    707        stream.defaultWriteObject();
    708        SerialUtilities.writePaint(this.paint, stream);
    709        SerialUtilities.writeStroke(this.stroke, stream);
    710        SerialUtilities.writePaint(this.labelPaint, stream);
    711        SerialUtilities.writePaint(this.labelBackgroundPaint, stream);
    712        SerialUtilities.writeStroke(this.labelOutlineStroke, stream);
    713        SerialUtilities.writePaint(this.labelOutlinePaint, stream);
    714    }
    715
    716    /**
    717     * Provides serialization support.
    718     *
    719     * @param stream  the input stream.
    720     *
    721     * @throws IOException  if there is an I/O error.
    722     * @throws ClassNotFoundException  if there is a classpath problem.
    723     */
    724    private void readObject(ObjectInputStream stream)
    725            throws IOException, ClassNotFoundException {
    726        stream.defaultReadObject();
    727        this.paint = SerialUtilities.readPaint(stream);
    728        this.stroke = SerialUtilities.readStroke(stream);
    729        this.labelPaint = SerialUtilities.readPaint(stream);
    730        this.labelBackgroundPaint = SerialUtilities.readPaint(stream);
    731        this.labelOutlineStroke = SerialUtilities.readStroke(stream);
    732        this.labelOutlinePaint = SerialUtilities.readPaint(stream);
    733        this.pcs = new PropertyChangeSupport(this);
    734    }
    735
    736}