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