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