    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    }