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     * XYTextAnnotation.java
029     * ---------------------
030     * (C) Copyright 2002-2007, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * $Id: XYTextAnnotation.java,v 1.5.2.4 2007/03/06 16:12:19 mungady Exp $
036     *
037     * Changes:
038     * --------
039     * 28-Aug-2002 : Version 1 (DG);
040     * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG);
041     * 13-Jan-2003 : Reviewed Javadocs (DG);
042     * 26-Mar-2003 : Implemented Serializable (DG);
043     * 02-Jul-2003 : Added new text alignment and rotation options (DG);
044     * 19-Aug-2003 : Implemented Cloneable (DG);
045     * 17-Jan-2003 : Added fix for bug 878706, where the annotation is placed 
046     *               incorrectly for a plot with horizontal orientation (thanks to
047     *               Ed Yu for the fix) (DG);
048     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
049     * ------------- JFREECHART 1.0.x ---------------------------------------------
050     * 26-Jan-2006 : Fixed equals() method (bug 1415480) (DG);
051     * 06-Mar-2007 : Added argument checks, re-implemented hashCode() method (DG);
052     *
053     */
054    
055    package org.jfree.chart.annotations;
056    
057    import java.awt.Color;
058    import java.awt.Font;
059    import java.awt.Graphics2D;
060    import java.awt.Paint;
061    import java.awt.Shape;
062    import java.awt.geom.Rectangle2D;
063    import java.io.IOException;
064    import java.io.ObjectInputStream;
065    import java.io.ObjectOutputStream;
066    import java.io.Serializable;
067    
068    import org.jfree.chart.HashUtilities;
069    import org.jfree.chart.axis.ValueAxis;
070    import org.jfree.chart.plot.Plot;
071    import org.jfree.chart.plot.PlotOrientation;
072    import org.jfree.chart.plot.PlotRenderingInfo;
073    import org.jfree.chart.plot.XYPlot;
074    import org.jfree.io.SerialUtilities;
075    import org.jfree.text.TextUtilities;
076    import org.jfree.ui.RectangleEdge;
077    import org.jfree.ui.TextAnchor;
078    import org.jfree.util.PaintUtilities;
079    import org.jfree.util.PublicCloneable;
080    
081    /**
082     * A text annotation that can be placed at a particular (x, y) location on an 
083     * {@link XYPlot}.
084     */
085    public class XYTextAnnotation extends AbstractXYAnnotation
086                                  implements Cloneable, PublicCloneable, 
087                                             Serializable {
088    
089        /** For serialization. */
090        private static final long serialVersionUID = -2946063342782506328L;
091        
092        /** The default font. */
093        public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 
094                10);
095    
096        /** The default paint. */
097        public static final Paint DEFAULT_PAINT = Color.black;
098        
099        /** The default text anchor. */
100        public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER;
101    
102        /** The default rotation anchor. */    
103        public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER;
104        
105        /** The default rotation angle. */
106        public static final double DEFAULT_ROTATION_ANGLE = 0.0;
107    
108        /** The text. */
109        private String text;
110    
111        /** The font. */
112        private Font font;
113    
114        /** The paint. */
115        private transient Paint paint;
116        
117        /** The x-coordinate. */
118        private double x;
119    
120        /** The y-coordinate. */
121        private double y;
122    
123        /** The text anchor (to be aligned with (x, y)). */
124        private TextAnchor textAnchor;
125        
126        /** The rotation anchor. */
127        private TextAnchor rotationAnchor;
128        
129        /** The rotation angle. */
130        private double rotationAngle;
131        
132        /**
133         * Creates a new annotation to be displayed at the given coordinates.  The
134         * coordinates are specified in data space (they will be converted to 
135         * Java2D space for display).
136         *
137         * @param text  the text (<code>null</code> not permitted).
138         * @param x  the x-coordinate (in data space).
139         * @param y  the y-coordinate (in data space).
140         */
141        public XYTextAnnotation(String text, double x, double y) {
142            if (text == null) {
143                throw new IllegalArgumentException("Null 'text' argument.");
144            }
145            this.text = text;
146            this.font = DEFAULT_FONT;
147            this.paint = DEFAULT_PAINT;
148            this.x = x;
149            this.y = y;
150            this.textAnchor = DEFAULT_TEXT_ANCHOR;
151            this.rotationAnchor = DEFAULT_ROTATION_ANCHOR;
152            this.rotationAngle = DEFAULT_ROTATION_ANGLE;
153        }
154        
155        /**
156         * Returns the text for the annotation.
157         *
158         * @return The text (never <code>null</code>).
159         * 
160         * @see #setText(String)
161         */
162        public String getText() {
163            return this.text;
164        }
165    
166        /**
167         * Sets the text for the annotation.
168         * 
169         * @param text  the text (<code>null</code> not permitted).
170         * 
171         * @see #getText()
172         */
173        public void setText(String text) {
174            if (text == null) {
175                throw new IllegalArgumentException("Null 'text' argument.");
176            }
177            this.text = text;
178        }
179        
180        /**
181         * Returns the font for the annotation.
182         *
183         * @return The font (never <code>null</code>).
184         * 
185         * @see #setFont(Font)
186         */
187        public Font getFont() {
188            return this.font;
189        }
190    
191        /**
192         * Sets the font for the annotation.
193         * 
194         * @param font  the font (<code>null</code> not permitted).
195         * 
196         * @see #getFont()
197         */
198        public void setFont(Font font) {
199            if (font == null) {
200                throw new IllegalArgumentException("Null 'font' argument.");
201            }
202            this.font = font;
203        }
204        
205        /**
206         * Returns the paint for the annotation.
207         *
208         * @return The paint (never <code>null</code>).
209         * 
210         * @see #setPaint(Paint)
211         */
212        public Paint getPaint() {
213            return this.paint;
214        }
215        
216        /**
217         * Sets the paint for the annotation.
218         * 
219         * @param paint  the paint (<code>null</code> not permitted).
220         * 
221         * @see #getPaint()
222         */
223        public void setPaint(Paint paint) {
224            if (paint == null) {
225                throw new IllegalArgumentException("Null 'paint' argument.");
226            }
227            this.paint = paint;
228        }
229    
230        /**
231         * Returns the text anchor.
232         * 
233         * @return The text anchor (never <code>null</code>).
234         * 
235         * @see #setTextAnchor(TextAnchor)
236         */
237        public TextAnchor getTextAnchor() {
238            return this.textAnchor;
239        }
240        
241        /**
242         * Sets the text anchor (the point on the text bounding rectangle that is 
243         * aligned to the (x, y) coordinate of the annotation).
244         * 
245         * @param anchor  the anchor point (<code>null</code> not permitted).
246         * 
247         * @see #getTextAnchor()
248         */
249        public void setTextAnchor(TextAnchor anchor) {
250            if (anchor == null) {
251                throw new IllegalArgumentException("Null 'anchor' argument.");
252            }
253            this.textAnchor = anchor;
254        }
255        
256        /**
257         * Returns the rotation anchor.
258         * 
259         * @return The rotation anchor point (never <code>null</code>).
260         * 
261         * @see #setRotationAnchor(TextAnchor)
262         */
263        public TextAnchor getRotationAnchor() {
264            return this.rotationAnchor;
265        }
266        
267        /**
268         * Sets the rotation anchor point.
269         * 
270         * @param anchor  the anchor (<code>null</code> not permitted).
271         * 
272         * @see #getRotationAnchor()
273         */
274        public void setRotationAnchor(TextAnchor anchor) {
275            if (anchor == null) {
276                throw new IllegalArgumentException("Null 'anchor' argument.");
277            }
278            this.rotationAnchor = anchor;    
279        }
280        
281        /**
282         * Returns the rotation angle.
283         * 
284         * @return The rotation angle.
285         * 
286         * @see #setRotationAngle(double)
287         */
288        public double getRotationAngle() {
289            return this.rotationAngle; 
290        }
291        
292        /**
293         * Sets the rotation angle.  The angle is measured clockwise in radians.
294         * 
295         * @param angle  the angle (in radians).
296         * 
297         * @see #getRotationAngle()
298         */
299        public void setRotationAngle(double angle) {
300            this.rotationAngle = angle;    
301        }
302        
303        /**
304         * Returns the x coordinate for the text anchor point (measured against the
305         * domain axis).
306         * 
307         * @return The x coordinate (in data space).
308         * 
309         * @see #setX(double)
310         */
311        public double getX() {
312            return this.x;
313        }
314        
315        /**
316         * Sets the x coordinate for the text anchor point (measured against the 
317         * domain axis).
318         * 
319         * @param x  the x coordinate (in data space).
320         * 
321         * @see #getX()
322         */
323        public void setX(double x) {
324            this.x = x;
325        }
326        
327        /**
328         * Returns the y coordinate for the text anchor point (measured against the
329         * range axis).
330         * 
331         * @return The y coordinate (in data space).
332         * 
333         * @see #setY(double)
334         */
335        public double getY() {
336            return this.y;
337        }
338        
339        /**
340         * Sets the y coordinate for the text anchor point (measured against the
341         * range axis).
342         * 
343         * @param y  the y coordinate.
344         * 
345         * @see #getY()
346         */
347        public void setY(double y) {
348            this.y = y;
349        }    
350    
351        /**
352         * Draws the annotation.
353         *
354         * @param g2  the graphics device.
355         * @param plot  the plot.
356         * @param dataArea  the data area.
357         * @param domainAxis  the domain axis.
358         * @param rangeAxis  the range axis.
359         * @param rendererIndex  the renderer index.
360         * @param info  an optional info object that will be populated with
361         *              entity information.
362         */
363        public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
364                         ValueAxis domainAxis, ValueAxis rangeAxis, 
365                         int rendererIndex,
366                         PlotRenderingInfo info) {
367    
368            PlotOrientation orientation = plot.getOrientation();
369            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
370                    plot.getDomainAxisLocation(), orientation);
371            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
372                    plot.getRangeAxisLocation(), orientation);
373    
374            float anchorX = (float) domainAxis.valueToJava2D(
375                    this.x, dataArea, domainEdge);
376            float anchorY = (float) rangeAxis.valueToJava2D(
377                    this.y, dataArea, rangeEdge);
378    
379            if (orientation == PlotOrientation.HORIZONTAL) {
380                float tempAnchor = anchorX;
381                anchorX = anchorY;
382                anchorY = tempAnchor;
383            }
384            
385            g2.setFont(getFont());
386            g2.setPaint(getPaint());
387            TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
388                    getTextAnchor(), getRotationAngle(), getRotationAnchor());
389            Shape hotspot = TextUtilities.calculateRotatedStringBounds(
390                    getText(), g2, anchorX, anchorY, getTextAnchor(), 
391                    getRotationAngle(), getRotationAnchor());
392            
393            String toolTip = getToolTipText();
394            String url = getURL();
395            if (toolTip != null || url != null) {
396                addEntity(info, hotspot, rendererIndex, toolTip, url);
397            }
398    
399        }
400        
401        /**
402         * Tests this annotation for equality with an arbitrary object.
403         * 
404         * @param obj  the object (<code>null</code> permitted).
405         * 
406         * @return A boolean.
407         */
408        public boolean equals(Object obj) {
409            if (obj == this) {
410                return true;   
411            }
412            if (!(obj instanceof XYTextAnnotation)) {
413                return false;   
414            }
415            if (!super.equals(obj)) {
416                return false;
417            }
418            XYTextAnnotation that = (XYTextAnnotation) obj;
419            if (!this.text.equals(that.text)) {
420                return false;   
421            }
422            if (this.x != that.x) {
423                return false;
424            }
425            if (this.y != that.y) {
426                return false;
427            }
428            if (!this.font.equals(that.font)) {
429                return false;   
430            }
431            if (!PaintUtilities.equal(this.paint, that.paint)) {
432                return false;   
433            }
434            if (!this.rotationAnchor.equals(that.rotationAnchor)) {
435                return false;   
436            }
437            if (this.rotationAngle != that.rotationAngle) {
438                return false;   
439            }
440            if (!this.textAnchor.equals(that.textAnchor)) {
441                return false;   
442            }
443            return true;   
444        }
445        
446        /**
447         * Returns a hash code for the object.
448         * 
449         * @return A hash code.
450         */
451        public int hashCode() {
452            int result = 193;
453            result = 37 * this.text.hashCode();
454            result = 37 * this.font.hashCode();
455            result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
456            long temp = Double.doubleToLongBits(this.x);
457            result = 37 * result + (int) (temp ^ (temp >>> 32));
458            temp = Double.doubleToLongBits(this.y);
459            result = 37 * result + (int) (temp ^ (temp >>> 32));
460            result = 37 * result + this.textAnchor.hashCode();
461            result = 37 * result + this.rotationAnchor.hashCode();
462            temp = Double.doubleToLongBits(this.rotationAngle);
463            result = 37 * result + (int) (temp ^ (temp >>> 32));
464            return result;   
465        }
466        
467        /**
468         * Returns a clone of the annotation.
469         * 
470         * @return A clone.
471         * 
472         * @throws CloneNotSupportedException  if the annotation can't be cloned.
473         */
474        public Object clone() throws CloneNotSupportedException {
475            return super.clone();
476        }
477        
478        /**
479         * Provides serialization support.
480         *
481         * @param stream  the output stream.
482         *
483         * @throws IOException  if there is an I/O error.
484         */
485        private void writeObject(ObjectOutputStream stream) throws IOException {
486            stream.defaultWriteObject();
487            SerialUtilities.writePaint(this.paint, stream);
488        }
489    
490        /**
491         * Provides serialization support.
492         *
493         * @param stream  the input stream.
494         *
495         * @throws IOException  if there is an I/O error.
496         * @throws ClassNotFoundException  if there is a classpath problem.
497         */
498        private void readObject(ObjectInputStream stream) 
499            throws IOException, ClassNotFoundException {
500            stream.defaultReadObject();
501            this.paint = SerialUtilities.readPaint(stream);
502        }
503    
504    }