    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 * CategoryLineAnnotation.java
    029 * ---------------------------
    030 * (C) Copyright 2005-2011, by Object Refinery Limited.
    031 *
    032 * Original Author:  David Gilbert (for Object Refinery Limited);
    033 * Contributor(s):   Peter Kolb (patch 2809117);
    034 *
    035 * Changes:
    036 * --------
    037 * 29-Jul-2005 : Version 1, based on CategoryTextAnnotation (DG);
    038 * ------------- JFREECHART 1.0.x ---------------------------------------------
    039 * 06-Mar-2007 : Reimplemented hashCode() (DG);
    040 * 23-Apr-2008 : Implemented PublicCloneable (DG);
    041 * 24-Jun-2009 : Now extends AbstractAnnotation (see patch 2809117 by PK) (DG);
    042 *
    043 */
    044
    045package org.jfree.chart.annotations;
    046
    047import java.awt.BasicStroke;
    048import java.awt.Color;
    049import java.awt.Graphics2D;
    050import java.awt.Paint;
    051import java.awt.Stroke;
    052import java.awt.geom.Rectangle2D;
    053import java.io.IOException;
    054import java.io.ObjectInputStream;
    055import java.io.ObjectOutputStream;
    056import java.io.Serializable;
    057
    058import org.jfree.chart.HashUtilities;
    059import org.jfree.chart.axis.CategoryAnchor;
    060import org.jfree.chart.axis.CategoryAxis;
    061import org.jfree.chart.axis.ValueAxis;
    062import org.jfree.chart.event.AnnotationChangeEvent;
    063import org.jfree.chart.plot.CategoryPlot;
    064import org.jfree.chart.plot.Plot;
    065import org.jfree.chart.plot.PlotOrientation;
    066import org.jfree.data.category.CategoryDataset;
    067import org.jfree.io.SerialUtilities;
    068import org.jfree.ui.RectangleEdge;
    069import org.jfree.util.ObjectUtilities;
    070import org.jfree.util.PaintUtilities;
    071import org.jfree.util.PublicCloneable;
    072
    073/**
    074 * A line annotation that can be placed on a {@link CategoryPlot}.
    075 */
    076public class CategoryLineAnnotation extends AbstractAnnotation
    077        implements CategoryAnnotation, Cloneable, PublicCloneable,
    078        Serializable {
    079
    080    /** For serialization. */
    081    static final long serialVersionUID = 3477740483341587984L;
    082
    083    /** The category for the start of the line. */
    084    private Comparable category1;
    085
    086    /** The value for the start of the line. */
    087    private double value1;
    088
    089    /** The category for the end of the line. */
    090    private Comparable category2;
    091
    092    /** The value for the end of the line. */
    093    private double value2;
    094
    095    /** The line color. */
    096    private transient Paint paint = Color.black;
    097
    098    /** The line stroke. */
    099    private transient Stroke stroke = new BasicStroke(1.0f);
    100
    101    /**
    102     * Creates a new annotation that draws a line between (category1, value1)
    103     * and (category2, value2).
    104     *
    105     * @param category1  the category (<code>null</code> not permitted).
    106     * @param value1  the value.
    107     * @param category2  the category (<code>null</code> not permitted).
    108     * @param value2  the value.
    109     * @param paint  the line color (<code>null</code> not permitted).
    110     * @param stroke  the line stroke (<code>null</code> not permitted).
    111     */
    112    public CategoryLineAnnotation(Comparable category1, double value1,
    113                                  Comparable category2, double value2,
    114                                  Paint paint, Stroke stroke) {
    115        super();
    116        if (category1 == null) {
    117            throw new IllegalArgumentException("Null 'category1' argument.");
    118        }
    119        if (category2 == null) {
    120            throw new IllegalArgumentException("Null 'category2' argument.");
    121        }
    122        if (paint == null) {
    123            throw new IllegalArgumentException("Null 'paint' argument.");
    124        }
    125        if (stroke == null) {
    126            throw new IllegalArgumentException("Null 'stroke' argument.");
    127        }
    128        this.category1 = category1;
    129        this.value1 = value1;
    130        this.category2 = category2;
    131        this.value2 = value2;
    132        this.paint = paint;
    133        this.stroke = stroke;
    134    }
    135
    136    /**
    137     * Returns the category for the start of the line.
    138     *
    139     * @return The category for the start of the line (never <code>null</code>).
    140     *
    141     * @see #setCategory1(Comparable)
    142     */
    143    public Comparable getCategory1() {
    144        return this.category1;
    145    }
    146
    147    /**
    148     * Sets the category for the start of the line and sends an
    149     * {@link AnnotationChangeEvent} to all registered listeners.
    150     *
    151     * @param category  the category (<code>null</code> not permitted).
    152     *
    153     * @see #getCategory1()
    154     */
    155    public void setCategory1(Comparable category) {
    156        if (category == null) {
    157            throw new IllegalArgumentException("Null 'category' argument.");
    158        }
    159        this.category1 = category;
    160        fireAnnotationChanged();
    161    }
    162
    163    /**
    164     * Returns the y-value for the start of the line.
    165     *
    166     * @return The y-value for the start of the line.
    167     *
    168     * @see #setValue1(double)
    169     */
    170    public double getValue1() {
    171        return this.value1;
    172    }
    173
    174    /**
    175     * Sets the y-value for the start of the line and sends an
    176     * {@link AnnotationChangeEvent} to all registered listeners.
    177     *
    178     * @param value  the value.
    179     *
    180     * @see #getValue1()
    181     */
    182    public void setValue1(double value) {
    183        this.value1 = value;
    184        fireAnnotationChanged();
    185    }
    186
    187    /**
    188     * Returns the category for the end of the line.
    189     *
    190     * @return The category for the end of the line (never <code>null</code>).
    191     *
    192     * @see #setCategory2(Comparable)
    193     */
    194    public Comparable getCategory2() {
    195        return this.category2;
    196    }
    197
    198    /**
    199     * Sets the category for the end of the line and sends an
    200     * {@link AnnotationChangeEvent} to all registered listeners.
    201     *
    202     * @param category  the category (<code>null</code> not permitted).
    203     *
    204     * @see #getCategory2()
    205     */
    206    public void setCategory2(Comparable category) {
    207        if (category == null) {
    208            throw new IllegalArgumentException("Null 'category' argument.");
    209        }
    210        this.category2 = category;
    211        fireAnnotationChanged();
    212    }
    213
    214    /**
    215     * Returns the y-value for the end of the line.
    216     *
    217     * @return The y-value for the end of the line.
    218     *
    219     * @see #setValue2(double)
    220     */
    221    public double getValue2() {
    222        return this.value2;
    223    }
    224
    225    /**
    226     * Sets the y-value for the end of the line and sends an
    227     * {@link AnnotationChangeEvent} to all registered listeners.
    228     *
    229     * @param value  the value.
    230     *
    231     * @see #getValue2()
    232     */
    233    public void setValue2(double value) {
    234        this.value2 = value;
    235        fireAnnotationChanged();
    236    }
    237
    238    /**
    239     * Returns the paint used to draw the connecting line.
    240     *
    241     * @return The paint (never <code>null</code>).
    242     *
    243     * @see #setPaint(Paint)
    244     */
    245    public Paint getPaint() {
    246        return this.paint;
    247    }
    248
    249    /**
    250     * Sets the paint used to draw the connecting line and sends an
    251     * {@link AnnotationChangeEvent} to all registered listeners.
    252     *
    253     * @param paint  the paint (<code>null</code> not permitted).
    254     *
    255     * @see #getPaint()
    256     */
    257    public void setPaint(Paint paint) {
    258        if (paint == null) {
    259            throw new IllegalArgumentException("Null 'paint' argument.");
    260        }
    261        this.paint = paint;
    262        fireAnnotationChanged();
    263    }
    264
    265    /**
    266     * Returns the stroke used to draw the connecting line.
    267     *
    268     * @return The stroke (never <code>null</code>).
    269     *
    270     * @see #setStroke(Stroke)
    271     */
    272    public Stroke getStroke() {
    273        return this.stroke;
    274    }
    275
    276    /**
    277     * Sets the stroke used to draw the connecting line and sends an
    278     * {@link AnnotationChangeEvent} to all registered listeners.
    279     *
    280     * @param stroke  the stroke (<code>null</code> not permitted).
    281     *
    282     * @see #getStroke()
    283     */
    284    public void setStroke(Stroke stroke) {
    285        if (stroke == null) {
    286            throw new IllegalArgumentException("Null 'stroke' argument.");
    287        }
    288        this.stroke = stroke;
    289        fireAnnotationChanged();
    290    }
    291
    292    /**
    293     * Draws the annotation.
    294     *
    295     * @param g2  the graphics device.
    296     * @param plot  the plot.
    297     * @param dataArea  the data area.
    298     * @param domainAxis  the domain axis.
    299     * @param rangeAxis  the range axis.
    300     */
    301    public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
    302                     CategoryAxis domainAxis, ValueAxis rangeAxis) {
    303
    304        CategoryDataset dataset = plot.getDataset();
    305        int catIndex1 = dataset.getColumnIndex(this.category1);
    306        int catIndex2 = dataset.getColumnIndex(this.category2);
    307        int catCount = dataset.getColumnCount();
    308
    309        double lineX1 = 0.0f;
    310        double lineY1 = 0.0f;
    311        double lineX2 = 0.0f;
    312        double lineY2 = 0.0f;
    313        PlotOrientation orientation = plot.getOrientation();
    314        RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
    315            plot.getDomainAxisLocation(), orientation);
    316        RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
    317            plot.getRangeAxisLocation(), orientation);
    318
    319        if (orientation == PlotOrientation.HORIZONTAL) {
    320            lineY1 = domainAxis.getCategoryJava2DCoordinate(
    321                CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
    322                domainEdge);
    323            lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
    324            lineY2 = domainAxis.getCategoryJava2DCoordinate(
    325                CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
    326                domainEdge);
    327            lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
    328        }
    329        else if (orientation == PlotOrientation.VERTICAL) {
    330            lineX1 = domainAxis.getCategoryJava2DCoordinate(
    331                CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea,
    332                domainEdge);
    333            lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge);
    334            lineX2 = domainAxis.getCategoryJava2DCoordinate(
    335                CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea,
    336                domainEdge);
    337            lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge);
    338        }
    339        g2.setPaint(this.paint);
    340        g2.setStroke(this.stroke);
    341        g2.drawLine((int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2);
    342    }
    343
    344    /**
    345     * Tests this object for equality with another.
    346     *
    347     * @param obj  the object (<code>null</code> permitted).
    348     *
    349     * @return <code>true</code> or <code>false</code>.
    350     */
    351    public boolean equals(Object obj) {
    352        if (obj == this) {
    353            return true;
    354        }
    355        if (!(obj instanceof CategoryLineAnnotation)) {
    356            return false;
    357        }
    358        CategoryLineAnnotation that = (CategoryLineAnnotation) obj;
    359        if (!this.category1.equals(that.getCategory1())) {
    360            return false;
    361        }
    362        if (this.value1 != that.getValue1()) {
    363            return false;
    364        }
    365        if (!this.category2.equals(that.getCategory2())) {
    366            return false;
    367        }
    368        if (this.value2 != that.getValue2()) {
    369            return false;
    370        }
    371        if (!PaintUtilities.equal(this.paint, that.paint)) {
    372            return false;
    373        }
    374        if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
    375            return false;
    376        }
    377        return true;
    378    }
    379
    380    /**
    381     * Returns a hash code for this instance.
    382     *
    383     * @return A hash code.
    384     */
    385    public int hashCode() {
    386        int result = 193;
    387        result = 37 * result + this.category1.hashCode();
    388        long temp = Double.doubleToLongBits(this.value1);
    389        result = 37 * result + (int) (temp ^ (temp >>> 32));
    390        result = 37 * result + this.category2.hashCode();
    391        temp = Double.doubleToLongBits(this.value2);
    392        result = 37 * result + (int) (temp ^ (temp >>> 32));
    393        result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
    394        result = 37 * result + this.stroke.hashCode();
    395        return result;
    396    }
    397
    398    /**
    399     * Returns a clone of the annotation.
    400     *
    401     * @return A clone.
    402     *
    403     * @throws CloneNotSupportedException  this class will not throw this
    404     *         exception, but subclasses (if any) might.
    405     */
    406    public Object clone() throws CloneNotSupportedException {
    407        return super.clone();
    408    }
    409
    410    /**
    411     * Provides serialization support.
    412     *
    413     * @param stream  the output stream.
    414     *
    415     * @throws IOException if there is an I/O error.
    416     */
    417    private void writeObject(ObjectOutputStream stream) throws IOException {
    418        stream.defaultWriteObject();
    419        SerialUtilities.writePaint(this.paint, stream);
    420        SerialUtilities.writeStroke(this.stroke, stream);
    421    }
    422
    423    /**
    424     * Provides serialization support.
    425     *
    426     * @param stream  the input stream.
    427     *
    428     * @throws IOException  if there is an I/O error.
    429     * @throws ClassNotFoundException  if there is a classpath problem.
    430     */
    431    private void readObject(ObjectInputStream stream)
    432        throws IOException, ClassNotFoundException {
    433        stream.defaultReadObject();
    434        this.paint = SerialUtilities.readPaint(stream);
    435        this.stroke = SerialUtilities.readStroke(stream);
    436    }
    437
    438}