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     * CategoryTextAnnotation.java
029     * ---------------------------
030     * (C) Copyright 2003-2011, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Peter Kolb (patch 2809117);
034     *
035     * Changes:
036     * --------
037     * 02-Apr-2003 : Version 1 (DG);
038     * 02-Jul-2003 : Added new text alignment and rotation options (DG);
039     * 04-Jul-2003 : Added a category anchor option (DG);
040     * 19-Aug-2003 : Added equals() method and implemented Cloneable (DG);
041     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
042     * 30-Sep-2004 : Moved drawRotatedString() from RefineryUtilities
043     *               --> TextUtilities (DG);
044     * ------------- JFREECHART 1.0.x -------------------------------------------
045     * 06-Mar-2007 : Implemented hashCode() (DG);
046     * 23-Apr-2008 : Implemented PublicCloneable (DG);
047     * 24-Jun-2009 : Fire change events (see patch 2809117 by PK) (DG);
048     *
049     */
050    
051    package org.jfree.chart.annotations;
052    
053    import java.awt.Graphics2D;
054    import java.awt.geom.Rectangle2D;
055    import java.io.Serializable;
056    
057    import org.jfree.chart.axis.CategoryAnchor;
058    import org.jfree.chart.axis.CategoryAxis;
059    import org.jfree.chart.axis.ValueAxis;
060    import org.jfree.chart.event.AnnotationChangeEvent;
061    import org.jfree.chart.plot.CategoryPlot;
062    import org.jfree.chart.plot.Plot;
063    import org.jfree.chart.plot.PlotOrientation;
064    import org.jfree.data.category.CategoryDataset;
065    import org.jfree.text.TextUtilities;
066    import org.jfree.ui.RectangleEdge;
067    import org.jfree.util.PublicCloneable;
068    
069    /**
070     * A text annotation that can be placed on a {@link CategoryPlot}.
071     */
072    public class CategoryTextAnnotation extends TextAnnotation
073            implements CategoryAnnotation, Cloneable, PublicCloneable,
074                       Serializable {
075    
076        /** For serialization. */
077        private static final long serialVersionUID = 3333360090781320147L;
078    
079        /** The category. */
080        private Comparable category;
081    
082        /** The category anchor (START, MIDDLE, or END). */
083        private CategoryAnchor categoryAnchor;
084    
085        /** The value. */
086        private double value;
087    
088        /**
089         * Creates a new annotation to be displayed at the given location.
090         *
091         * @param text  the text (<code>null</code> not permitted).
092         * @param category  the category (<code>null</code> not permitted).
093         * @param value  the value.
094         */
095        public CategoryTextAnnotation(String text, Comparable category,
096                                      double value) {
097            super(text);
098            if (category == null) {
099                throw new IllegalArgumentException("Null 'category' argument.");
100            }
101            this.category = category;
102            this.value = value;
103            this.categoryAnchor = CategoryAnchor.MIDDLE;
104        }
105    
106        /**
107         * Returns the category.
108         *
109         * @return The category (never <code>null</code>).
110         *
111         * @see #setCategory(Comparable)
112         */
113        public Comparable getCategory() {
114            return this.category;
115        }
116    
117        /**
118         * Sets the category that the annotation attaches to and sends an
119         * {@link AnnotationChangeEvent} to all registered listeners.
120         *
121         * @param category  the category (<code>null</code> not permitted).
122         *
123         * @see #getCategory()
124         */
125        public void setCategory(Comparable category) {
126            if (category == null) {
127                throw new IllegalArgumentException("Null 'category' argument.");
128            }
129            this.category = category;
130            fireAnnotationChanged();
131        }
132    
133        /**
134         * Returns the category anchor point.
135         *
136         * @return The category anchor point.
137         *
138         * @see #setCategoryAnchor(CategoryAnchor)
139         */
140        public CategoryAnchor getCategoryAnchor() {
141            return this.categoryAnchor;
142        }
143    
144        /**
145         * Sets the category anchor point and sends an
146         * {@link AnnotationChangeEvent} to all registered listeners.
147         *
148         * @param anchor  the anchor point (<code>null</code> not permitted).
149         *
150         * @see #getCategoryAnchor()
151         */
152        public void setCategoryAnchor(CategoryAnchor anchor) {
153            if (anchor == null) {
154                throw new IllegalArgumentException("Null 'anchor' argument.");
155            }
156            this.categoryAnchor = anchor;
157            fireAnnotationChanged();
158        }
159    
160        /**
161         * Returns the value that the annotation attaches to.
162         *
163         * @return The value.
164         *
165         * @see #setValue(double)
166         */
167        public double getValue() {
168            return this.value;
169        }
170    
171        /**
172         * Sets the value and sends an
173         * {@link AnnotationChangeEvent} to all registered listeners.
174         *
175         * @param value  the value.
176         *
177         * @see #getValue()
178         */
179        public void setValue(double value) {
180            this.value = value;
181            fireAnnotationChanged();
182        }
183    
184        /**
185         * Draws the annotation.
186         *
187         * @param g2  the graphics device.
188         * @param plot  the plot.
189         * @param dataArea  the data area.
190         * @param domainAxis  the domain axis.
191         * @param rangeAxis  the range axis.
192         */
193        public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea,
194                         CategoryAxis domainAxis, ValueAxis rangeAxis) {
195    
196            CategoryDataset dataset = plot.getDataset();
197            int catIndex = dataset.getColumnIndex(this.category);
198            int catCount = dataset.getColumnCount();
199    
200            float anchorX = 0.0f;
201            float anchorY = 0.0f;
202            PlotOrientation orientation = plot.getOrientation();
203            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
204                    plot.getDomainAxisLocation(), orientation);
205            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
206                    plot.getRangeAxisLocation(), orientation);
207    
208            if (orientation == PlotOrientation.HORIZONTAL) {
209                anchorY = (float) domainAxis.getCategoryJava2DCoordinate(
210                        this.categoryAnchor, catIndex, catCount, dataArea,
211                        domainEdge);
212                anchorX = (float) rangeAxis.valueToJava2D(this.value, dataArea,
213                        rangeEdge);
214            }
215            else if (orientation == PlotOrientation.VERTICAL) {
216                anchorX = (float) domainAxis.getCategoryJava2DCoordinate(
217                        this.categoryAnchor, catIndex, catCount, dataArea,
218                        domainEdge);
219                anchorY = (float) rangeAxis.valueToJava2D(this.value, dataArea,
220                        rangeEdge);
221            }
222            g2.setFont(getFont());
223            g2.setPaint(getPaint());
224            TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
225                    getTextAnchor(), getRotationAngle(), getRotationAnchor());
226    
227        }
228    
229        /**
230         * Tests this object for equality with another.
231         *
232         * @param obj  the object (<code>null</code> permitted).
233         *
234         * @return <code>true</code> or <code>false</code>.
235         */
236        public boolean equals(Object obj) {
237            if (obj == this) {
238                return true;
239            }
240            if (!(obj instanceof CategoryTextAnnotation)) {
241                return false;
242            }
243            CategoryTextAnnotation that = (CategoryTextAnnotation) obj;
244            if (!super.equals(obj)) {
245                return false;
246            }
247            if (!this.category.equals(that.getCategory())) {
248                return false;
249            }
250            if (!this.categoryAnchor.equals(that.getCategoryAnchor())) {
251                return false;
252            }
253            if (this.value != that.getValue()) {
254                return false;
255            }
256            return true;
257        }
258    
259        /**
260         * Returns a hash code for this instance.
261         *
262         * @return A hash code.
263         */
264        public int hashCode() {
265            int result = super.hashCode();
266            result = 37 * result + this.category.hashCode();
267            result = 37 * result + this.categoryAnchor.hashCode();
268            long temp = Double.doubleToLongBits(this.value);
269            result = 37 * result + (int) (temp ^ (temp >>> 32));
270            return result;
271        }
272    
273        /**
274         * Returns a clone of the annotation.
275         *
276         * @return A clone.
277         *
278         * @throws CloneNotSupportedException  this class will not throw this
279         *         exception, but subclasses (if any) might.
280         */
281        public Object clone() throws CloneNotSupportedException {
282            return super.clone();
283        }
284    
285    }