    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2009, 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-2009, 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     * 12-Feb-2009 : Added background paint and outline paint/stroke (DG);
    051     * 01-Apr-2009 : Fixed bug in hotspot calculation (DG);
    052     *
    053     */
    054
    055    package org.jfree.chart.annotations;
    056
    057    import java.awt.BasicStroke;
    058    import java.awt.Color;
    059    import java.awt.Font;
    060    import java.awt.Graphics2D;
    061    import java.awt.Paint;
    062    import java.awt.Shape;
    063    import java.awt.Stroke;
    064    import java.awt.geom.Rectangle2D;
    065    import java.io.IOException;
    066    import java.io.ObjectInputStream;
    067    import java.io.ObjectOutputStream;
    068    import java.io.Serializable;
    069
    070    import org.jfree.chart.HashUtilities;
    071    import org.jfree.chart.axis.ValueAxis;
    072    import org.jfree.chart.plot.Plot;
    073    import org.jfree.chart.plot.PlotOrientation;
    074    import org.jfree.chart.plot.PlotRenderingInfo;
    075    import org.jfree.chart.plot.XYPlot;
    076    import org.jfree.io.SerialUtilities;
    077    import org.jfree.text.TextUtilities;
    078    import org.jfree.ui.RectangleEdge;
    079    import org.jfree.ui.TextAnchor;
    080    import org.jfree.util.PaintUtilities;
    081    import org.jfree.util.PublicCloneable;
    082
    083    /**
    084     * A text annotation that can be placed at a particular (x, y) location on an
    085     * {@link XYPlot}.
    086     */
    087    public class XYTextAnnotation extends AbstractXYAnnotation
    088            implements Cloneable, PublicCloneable, Serializable {
    089
    090        /** For serialization. */
    091        private static final long serialVersionUID = -2946063342782506328L;
    092
    093        /** The default font. */
    094        public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN,
    095                10);
    096
    097        /** The default paint. */
    098        public static final Paint DEFAULT_PAINT = Color.black;
    099
    100        /** The default text anchor. */
    101        public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER;
    102
    103        /** The default rotation anchor. */
    104        public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER;
    105
    106        /** The default rotation angle. */
    107        public static final double DEFAULT_ROTATION_ANGLE = 0.0;
    108
    109        /** The text. */
    110        private String text;
    111
    112        /** The font. */
    113        private Font font;
    114
    115        /** The paint. */
    116        private transient Paint paint;
    117
    118        /** The x-coordinate. */
    119        private double x;
    120
    121        /** The y-coordinate. */
    122        private double y;
    123
    124        /** The text anchor (to be aligned with (x, y)). */
    125        private TextAnchor textAnchor;
    126
    127        /** The rotation anchor. */
    128        private TextAnchor rotationAnchor;
    129
    130        /** The rotation angle. */
    131        private double rotationAngle;
    132
    133        /**
    134         * The background paint (possibly null).
    135         *
    136         * @since 1.0.13
    137         */
    138        private transient Paint backgroundPaint;
    139
    140        /**
    141         * The flag that controls the visibility of the outline.
    142         *
    143         * @since 1.0.13
    144         */
    145        private boolean outlineVisible;
    146
    147        /**
    148         * The outline paint (never null).
    149         *
    150         * @since 1.0.13
    151         */
    152        private transient Paint outlinePaint;
    153
    154        /**
    155         * The outline stroke (never null).
    156         *
    157         * @since 1.0.13
    158         */
    159        private transient Stroke outlineStroke;
    160
    161        /**
    162         * Creates a new annotation to be displayed at the given coordinates.  The
    163         * coordinates are specified in data space (they will be converted to
    164         * Java2D space for display).
    165         *
    166         * @param text  the text (<code>null</code> not permitted).
    167         * @param x  the x-coordinate (in data space).
    168         * @param y  the y-coordinate (in data space).
    169         */
    170        public XYTextAnnotation(String text, double x, double y) {
    171            if (text == null) {
    172                throw new IllegalArgumentException("Null 'text' argument.");
    173            }
    174            this.text = text;
    175            this.font = DEFAULT_FONT;
    176            this.paint = DEFAULT_PAINT;
    177            this.x = x;
    178            this.y = y;
    179            this.textAnchor = DEFAULT_TEXT_ANCHOR;
    180            this.rotationAnchor = DEFAULT_ROTATION_ANCHOR;
    181            this.rotationAngle = DEFAULT_ROTATION_ANGLE;
    182
    183            // by default the outline and background won't be visible
    184            this.backgroundPaint = null;
    185            this.outlineVisible = false;
    186            this.outlinePaint = Color.black;
    187            this.outlineStroke = new BasicStroke(0.5f);
    188        }
    189
    190        /**
    191         * Returns the text for the annotation.
    192         *
    193         * @return The text (never <code>null</code>).
    194         *
    195         * @see #setText(String)
    196         */
    197        public String getText() {
    198            return this.text;
    199        }
    200
    201        /**
    202         * Sets the text for the annotation.
    203         *
    204         * @param text  the text (<code>null</code> not permitted).
    205         *
    206         * @see #getText()
    207         */
    208        public void setText(String text) {
    209            if (text == null) {
    210                throw new IllegalArgumentException("Null 'text' argument.");
    211            }
    212            this.text = text;
    213        }
    214
    215        /**
    216         * Returns the font for the annotation.
    217         *
    218         * @return The font (never <code>null</code>).
    219         *
    220         * @see #setFont(Font)
    221         */
    222        public Font getFont() {
    223            return this.font;
    224        }
    225
    226        /**
    227         * Sets the font for the annotation.
    228         *
    229         * @param font  the font (<code>null</code> not permitted).
    230         *
    231         * @see #getFont()
    232         */
    233        public void setFont(Font font) {
    234            if (font == null) {
    235                throw new IllegalArgumentException("Null 'font' argument.");
    236            }
    237            this.font = font;
    238        }
    239
    240        /**
    241         * Returns the paint for the annotation.
    242         *
    243         * @return The paint (never <code>null</code>).
    244         *
    245         * @see #setPaint(Paint)
    246         */
    247        public Paint getPaint() {
    248            return this.paint;
    249        }
    250
    251        /**
    252         * Sets the paint for the annotation.
    253         *
    254         * @param paint  the paint (<code>null</code> not permitted).
    255         *
    256         * @see #getPaint()
    257         */
    258        public void setPaint(Paint paint) {
    259            if (paint == null) {
    260                throw new IllegalArgumentException("Null 'paint' argument.");
    261            }
    262            this.paint = paint;
    263        }
    264
    265        /**
    266         * Returns the text anchor.
    267         *
    268         * @return The text anchor (never <code>null</code>).
    269         *
    270         * @see #setTextAnchor(TextAnchor)
    271         */
    272        public TextAnchor getTextAnchor() {
    273            return this.textAnchor;
    274        }
    275
    276        /**
    277         * Sets the text anchor (the point on the text bounding rectangle that is
    278         * aligned to the (x, y) coordinate of the annotation).
    279         *
    280         * @param anchor  the anchor point (<code>null</code> not permitted).
    281         *
    282         * @see #getTextAnchor()
    283         */
    284        public void setTextAnchor(TextAnchor anchor) {
    285            if (anchor == null) {
    286                throw new IllegalArgumentException("Null 'anchor' argument.");
    287            }
    288            this.textAnchor = anchor;
    289        }
    290
    291        /**
    292         * Returns the rotation anchor.
    293         *
    294         * @return The rotation anchor point (never <code>null</code>).
    295         *
    296         * @see #setRotationAnchor(TextAnchor)
    297         */
    298        public TextAnchor getRotationAnchor() {
    299            return this.rotationAnchor;
    300        }
    301
    302        /**
    303         * Sets the rotation anchor point.
    304         *
    305         * @param anchor  the anchor (<code>null</code> not permitted).
    306         *
    307         * @see #getRotationAnchor()
    308         */
    309        public void setRotationAnchor(TextAnchor anchor) {
    310            if (anchor == null) {
    311                throw new IllegalArgumentException("Null 'anchor' argument.");
    312            }
    313            this.rotationAnchor = anchor;
    314        }
    315
    316        /**
    317         * Returns the rotation angle.
    318         *
    319         * @return The rotation angle.
    320         *
    321         * @see #setRotationAngle(double)
    322         */
    323        public double getRotationAngle() {
    324            return this.rotationAngle;
    325        }
    326
    327        /**
    328         * Sets the rotation angle.  The angle is measured clockwise in radians.
    329         *
    330         * @param angle  the angle (in radians).
    331         *
    332         * @see #getRotationAngle()
    333         */
    334        public void setRotationAngle(double angle) {
    335            this.rotationAngle = angle;
    336        }
    337
    338        /**
    339         * Returns the x coordinate for the text anchor point (measured against the
    340         * domain axis).
    341         *
    342         * @return The x coordinate (in data space).
    343         *
    344         * @see #setX(double)
    345         */
    346        public double getX() {
    347            return this.x;
    348        }
    349
    350        /**
    351         * Sets the x coordinate for the text anchor point (measured against the
    352         * domain axis).
    353         *
    354         * @param x  the x coordinate (in data space).
    355         *
    356         * @see #getX()
    357         */
    358        public void setX(double x) {
    359            this.x = x;
    360        }
    361
    362        /**
    363         * Returns the y coordinate for the text anchor point (measured against the
    364         * range axis).
    365         *
    366         * @return The y coordinate (in data space).
    367         *
    368         * @see #setY(double)
    369         */
    370        public double getY() {
    371            return this.y;
    372        }
    373
    374        /**
    375         * Sets the y coordinate for the text anchor point (measured against the
    376         * range axis).
    377         *
    378         * @param y  the y coordinate.
    379         *
    380         * @see #getY()
    381         */
    382        public void setY(double y) {
    383            this.y = y;
    384        }
    385
    386        /**
    387         * Returns the background paint for the annotation.
    388         *
    389         * @return The background paint (possibly <code>null</code>).
    390         *
    391         * @see #setBackgroundPaint(Paint)
    392         *
    393         * @since 1.0.13
    394         */
    395        public Paint getBackgroundPaint() {
    396            return this.backgroundPaint;
    397        }
    398
    399        /**
    400         * Sets the background paint for the annotation.
    401         *
    402         * @param paint  the paint (<code>null</code> permitted).
    403         *
    404         * @see #getBackgroundPaint()
    405         *
    406         * @since 1.0.13
    407         */
    408        public void setBackgroundPaint(Paint paint) {
    409            this.backgroundPaint = paint;
    410        }
    411
    412        /**
    413         * Returns the outline paint for the annotation.
    414         *
    415         * @return The outline paint (never <code>null</code>).
    416         *
    417         * @see #setOutlinePaint(Paint)
    418         *
    419         * @since 1.0.13
    420         */
    421        public Paint getOutlinePaint() {
    422            return this.outlinePaint;
    423        }
    424
    425        /**
    426         * Sets the outline paint for the annotation.
    427         *
    428         * @param paint  the paint (<code>null</code> not permitted).
    429         *
    430         * @see #getOutlinePaint()
    431         *
    432         * @since 1.0.13
    433         */
    434        public void setOutlinePaint(Paint paint) {
    435            if (paint == null) {
    436                throw new IllegalArgumentException("Null 'paint' argument.");
    437            }
    438            this.outlinePaint = paint;
    439        }
    440
    441        /**
    442         * Returns the outline stroke for the annotation.
    443         *
    444         * @return The outline stroke (never <code>null</code>).
    445         *
    446         * @see #setOutlineStroke(Stroke)
    447         *
    448         * @since 1.0.13
    449         */
    450        public Stroke getOutlineStroke() {
    451            return this.outlineStroke;
    452        }
    453
    454        /**
    455         * Sets the outline stroke for the annotation.
    456         *
    457         * @param stroke  the stroke (<code>null</code> not permitted).
    458         *
    459         * @see #getOutlineStroke()
    460         *
    461         * @since 1.0.13
    462         */
    463        public void setOutlineStroke(Stroke stroke) {
    464            if (stroke == null) {
    465                throw new IllegalArgumentException("Null 'stroke' argument.");
    466            }
    467            this.outlineStroke = stroke;
    468        }
    469
    470        /**
    471         * Returns the flag that controls whether or not the outline is drawn.
    472         *
    473         * @return A boolean.
    474         *
    475         * @since 1.0.13
    476         */
    477        public boolean isOutlineVisible() {
    478            return this.outlineVisible;
    479        }
    480
    481        /**
    482         * Sets the flag that controls whether or not the outline is drawn.
    483         *
    484         * @param visible  the new flag value.
    485         *
    486         * @since 1.0.13
    487         */
    488        public void setOutlineVisible(boolean visible) {
    489            this.outlineVisible = visible;
    490        }
    491
    492        /**
    493         * Draws the annotation.
    494         *
    495         * @param g2  the graphics device.
    496         * @param plot  the plot.
    497         * @param dataArea  the data area.
    498         * @param domainAxis  the domain axis.
    499         * @param rangeAxis  the range axis.
    500         * @param rendererIndex  the renderer index.
    501         * @param info  an optional info object that will be populated with
    502         *              entity information.
    503         */
    504        public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
    505                         ValueAxis domainAxis, ValueAxis rangeAxis,
    506                         int rendererIndex,
    507                         PlotRenderingInfo info) {
    508
    509            PlotOrientation orientation = plot.getOrientation();
    510            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
    511                    plot.getDomainAxisLocation(), orientation);
    512            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
    513                    plot.getRangeAxisLocation(), orientation);
    514
    515            float anchorX = (float) domainAxis.valueToJava2D(
    516                    this.x, dataArea, domainEdge);
    517            float anchorY = (float) rangeAxis.valueToJava2D(
    518                    this.y, dataArea, rangeEdge);
    519
    520            if (orientation == PlotOrientation.HORIZONTAL) {
    521                float tempAnchor = anchorX;
    522                anchorX = anchorY;
    523                anchorY = tempAnchor;
    524            }
    525
    526            g2.setFont(getFont());
    527            Shape hotspot = TextUtilities.calculateRotatedStringBounds(
    528                    getText(), g2, anchorX, anchorY, getTextAnchor(),
    529                    getRotationAngle(), getRotationAnchor());
    530            if (this.backgroundPaint != null) {
    531                g2.setPaint(this.backgroundPaint);
    532                g2.fill(hotspot);
    533            }
    534            g2.setPaint(getPaint());
    535            TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY,
    536                    getTextAnchor(), getRotationAngle(), getRotationAnchor());
    537            if (this.outlineVisible) {
    538                g2.setStroke(this.outlineStroke);
    539                g2.setPaint(this.outlinePaint);
    540                g2.draw(hotspot);
    541            }
    542
    543            String toolTip = getToolTipText();
    544            String url = getURL();
    545            if (toolTip != null || url != null) {
    546                addEntity(info, hotspot, rendererIndex, toolTip, url);
    547            }
    548
    549        }
    550
    551        /**
    552         * Tests this annotation for equality with an arbitrary object.
    553         *
    554         * @param obj  the object (<code>null</code> permitted).
    555         *
    556         * @return A boolean.
    557         */
    558        public boolean equals(Object obj) {
    559            if (obj == this) {
    560                return true;
    561            }
    562            if (!(obj instanceof XYTextAnnotation)) {
    563                return false;
    564            }
    565            XYTextAnnotation that = (XYTextAnnotation) obj;
    566            if (!this.text.equals(that.text)) {
    567                return false;
    568            }
    569            if (this.x != that.x) {
    570                return false;
    571            }
    572            if (this.y != that.y) {
    573                return false;
    574            }
    575            if (!this.font.equals(that.font)) {
    576                return false;
    577            }
    578            if (!PaintUtilities.equal(this.paint, that.paint)) {
    579                return false;
    580            }
    581            if (!this.rotationAnchor.equals(that.rotationAnchor)) {
    582                return false;
    583            }
    584            if (this.rotationAngle != that.rotationAngle) {
    585                return false;
    586            }
    587            if (!this.textAnchor.equals(that.textAnchor)) {
    588                return false;
    589            }
    590            if (this.outlineVisible != that.outlineVisible) {
    591                return false;
    592            }
    593            if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) {
    594                return false;
    595            }
    596            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
    597                return false;
    598            }
    599            if (!(this.outlineStroke.equals(that.outlineStroke))) {
    600                return false;
    601            }
    602            return super.equals(obj);
    603        }
    604
    605        /**
    606         * Returns a hash code for the object.
    607         *
    608         * @return A hash code.
    609         */
    610        public int hashCode() {
    611            int result = 193;
    612            result = 37 * this.text.hashCode();
    613            result = 37 * this.font.hashCode();
    614            result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
    615            long temp = Double.doubleToLongBits(this.x);
    616            result = 37 * result + (int) (temp ^ (temp >>> 32));
    617            temp = Double.doubleToLongBits(this.y);
    618            result = 37 * result + (int) (temp ^ (temp >>> 32));
    619            result = 37 * result + this.textAnchor.hashCode();
    620            result = 37 * result + this.rotationAnchor.hashCode();
    621            temp = Double.doubleToLongBits(this.rotationAngle);
    622            result = 37 * result + (int) (temp ^ (temp >>> 32));
    623            return result;
    624        }
    625
    626        /**
    627         * Returns a clone of the annotation.
    628         *
    629         * @return A clone.
    630         *
    631         * @throws CloneNotSupportedException  if the annotation can't be cloned.
    632         */
    633        public Object clone() throws CloneNotSupportedException {
    634            return super.clone();
    635        }
    636
    637        /**
    638         * Provides serialization support.
    639         *
    640         * @param stream  the output stream.
    641         *
    642         * @throws IOException  if there is an I/O error.
    643         */
    644        private void writeObject(ObjectOutputStream stream) throws IOException {
    645            stream.defaultWriteObject();
    646            SerialUtilities.writePaint(this.paint, stream);
    647            SerialUtilities.writePaint(this.backgroundPaint, stream);
    648            SerialUtilities.writePaint(this.outlinePaint, stream);
    649            SerialUtilities.writeStroke(this.outlineStroke, stream);
    650        }
    651
    652        /**
    653         * Provides serialization support.
    654         *
    655         * @param stream  the input stream.
    656         *
    657         * @throws IOException  if there is an I/O error.
    658         * @throws ClassNotFoundException  if there is a classpath problem.
    659         */
    660        private void readObject(ObjectInputStream stream)
    661            throws IOException, ClassNotFoundException {
    662            stream.defaultReadObject();
    663            this.paint = SerialUtilities.readPaint(stream);
    664            this.backgroundPaint = SerialUtilities.readPaint(stream);
    665            this.outlinePaint = SerialUtilities.readPaint(stream);
    666            this.outlineStroke = SerialUtilities.readStroke(stream);
    667        }
    668
    669    }