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