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