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}