    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     * DialTextAnnotation.java
    029     * -----------------------
    030     * (C) Copyright 2006-2008, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   -;
    034     *
    035     * Changes
    036     * -------
    037     * 03-Nov-2006 : Version 1 (DG);
    038     * 08-Mar-2007 : Fix in hashCode() (DG);
    039     * 17-Oct-2007 : Updated equals() (DG);
    040     * 24-Oct-2007 : Added getAnchor() and setAnchor() methods (DG);
    041     *
    042     */
    043
    044    package org.jfree.chart.plot.dial;
    045
    046    import java.awt.Color;
    047    import java.awt.Font;
    048    import java.awt.Graphics2D;
    049    import java.awt.Paint;
    050    import java.awt.geom.Arc2D;
    051    import java.awt.geom.Point2D;
    052    import java.awt.geom.Rectangle2D;
    053    import java.io.IOException;
    054    import java.io.ObjectInputStream;
    055    import java.io.ObjectOutputStream;
    056    import java.io.Serializable;
    057
    058    import org.jfree.chart.HashUtilities;
    059    import org.jfree.io.SerialUtilities;
    060    import org.jfree.text.TextUtilities;
    061    import org.jfree.ui.TextAnchor;
    062    import org.jfree.util.PaintUtilities;
    063    import org.jfree.util.PublicCloneable;
    064
    065    /**
    066     * A text annotation for a {@link DialPlot}.
    067     *
    068     * @since 1.0.7
    069     */
    070    public class DialTextAnnotation extends AbstractDialLayer implements DialLayer,
    071            Cloneable, PublicCloneable, Serializable {
    072
    073        /** For serialization. */
    074        static final long serialVersionUID = 3065267524054428071L;
    075
    076        /** The label text. */
    077        private String label;
    078
    079        /** The font. */
    080        private Font font;
    081
    082        /**
    083         * The paint for the label.  This field is transient because it requires
    084         * special handling for serialization.
    085         */
    086        private transient Paint paint;
    087
    088        /** The angle that defines the anchor point for the annotation. */
    089        private double angle;
    090
    091        /** The radius that defines the anchor point for the annotation. */
    092        private double radius;
    093
    094        /** The text anchor to be aligned to the annotation's anchor point. */
    095        private TextAnchor anchor;
    096
    097        /**
    098         * Creates a new instance of <code>DialTextAnnotation</code>.
    099         *
    100         * @param label  the label (<code>null</code> not permitted).
    101         */
    102        public DialTextAnnotation(String label) {
    103            if (label == null) {
    104                throw new IllegalArgumentException("Null 'label' argument.");
    105            }
    106            this.angle = -90.0;
    107            this.radius = 0.3;
    108            this.font = new Font("Dialog", Font.BOLD, 14);
    109            this.paint = Color.black;
    110            this.label = label;
    111            this.anchor = TextAnchor.TOP_CENTER;
    112        }
    113
    114        /**
    115         * Returns the label text.
    116         *
    117         * @return The label text (never <code>null</code).
    118         *
    119         * @see #setLabel(String)
    120         */
    121        public String getLabel() {
    122            return this.label;
    123        }
    124
    125        /**
    126         * Sets the label and sends a {@link DialLayerChangeEvent} to all
    127         * registered listeners.
    128         *
    129         * @param label  the label (<code>null</code> not permitted).
    130         *
    131         * @see #getLabel()
    132         */
    133        public void setLabel(String label) {
    134            if (label == null) {
    135                throw new IllegalArgumentException("Null 'label' argument.");
    136            }
    137            this.label = label;
    138            notifyListeners(new DialLayerChangeEvent(this));
    139        }
    140
    141        /**
    142         * Returns the font used to display the label.
    143         *
    144         * @return The font (never <code>null</code>).
    145         *
    146         * @see #setFont(Font)
    147         */
    148        public Font getFont() {
    149            return this.font;
    150        }
    151
    152        /**
    153         * Sets the font used to display the label and sends a
    154         * {@link DialLayerChangeEvent} to all registered listeners.
    155         *
    156         * @param font  the font (<code>null</code> not permitted).
    157         *
    158         * @see #getFont()
    159         */
    160        public void setFont(Font font) {
    161            if (font == null) {
    162                throw new IllegalArgumentException("Null 'font' argument.");
    163            }
    164            this.font = font;
    165            notifyListeners(new DialLayerChangeEvent(this));
    166        }
    167
    168        /**
    169         * Returns the paint used to display the label.
    170         *
    171         * @return The paint (never <code>null</code>).
    172         *
    173         * @see #setPaint(Paint)
    174         */
    175        public Paint getPaint() {
    176            return this.paint;
    177        }
    178
    179        /**
    180         * Sets the paint used to display the label and sends a
    181         * {@link DialLayerChangeEvent} to all registered listeners.
    182         *
    183         * @param paint  the paint (<code>null</code> not permitted).
    184         *
    185         * @see #getPaint()
    186         */
    187        public void setPaint(Paint paint) {
    188            if (paint == null) {
    189                throw new IllegalArgumentException("Null 'paint' argument.");
    190            }
    191            this.paint = paint;
    192            notifyListeners(new DialLayerChangeEvent(this));
    193        }
    194
    195        /**
    196         * Returns the angle used to calculate the anchor point.
    197         *
    198         * @return The angle (in degrees).
    199         *
    200         * @see #setAngle(double)
    201         * @see #getRadius()
    202         */
    203        public double getAngle() {
    204            return this.angle;
    205        }
    206
    207        /**
    208         * Sets the angle used to calculate the anchor point and sends a
    209         * {@link DialLayerChangeEvent} to all registered listeners.
    210         *
    211         * @param angle  the angle (in degrees).
    212         *
    213         * @see #getAngle()
    214         * @see #setRadius(double)
    215         */
    216        public void setAngle(double angle) {
    217            this.angle = angle;
    218            notifyListeners(new DialLayerChangeEvent(this));
    219        }
    220
    221        /**
    222         * Returns the radius used to calculate the anchor point.  This is
    223         * specified as a percentage relative to the dial's framing rectangle.
    224         *
    225         * @return The radius.
    226         *
    227         * @see #setRadius(double)
    228         * @see #getAngle()
    229         */
    230        public double getRadius() {
    231            return this.radius;
    232        }
    233
    234        /**
    235         * Sets the radius used to calculate the anchor point and sends a
    236         * {@link DialLayerChangeEvent} to all registered listeners.
    237         *
    238         * @param radius  the radius (as a percentage of the dial's framing
    239         *                rectangle).
    240         *
    241         * @see #getRadius()
    242         * @see #setAngle(double)
    243         */
    244        public void setRadius(double radius) {
    245            if (radius < 0.0) {
    246                throw new IllegalArgumentException(
    247                        "The 'radius' cannot be negative.");
    248            }
    249            this.radius = radius;
    250            notifyListeners(new DialLayerChangeEvent(this));
    251        }
    252
    253        /**
    254         * Returns the text anchor point that will be aligned to the position
    255         * specified by {@link #getAngle()} and {@link #getRadius()}.
    256         *
    257         * @return The anchor point.
    258         *
    259         * @see #setAnchor(TextAnchor)
    260         */
    261        public TextAnchor getAnchor() {
    262            return this.anchor;
    263        }
    264
    265        /**
    266         * Sets the text anchor point and sends a {@link DialLayerChangeEvent} to
    267         * all registered listeners.
    268         *
    269         * @param anchor  the anchor point (<code>null</code> not permitted).
    270         *
    271         * @see #getAnchor()
    272         */
    273        public void setAnchor(TextAnchor anchor) {
    274            if (anchor == null) {
    275                throw new IllegalArgumentException("Null 'anchor' argument.");
    276            }
    277            this.anchor = anchor;
    278            notifyListeners(new DialLayerChangeEvent(this));
    279        }
    280
    281        /**
    282         * Returns <code>true</code> to indicate that this layer should be
    283         * clipped within the dial window.
    284         *
    285         * @return <code>true</code>.
    286         */
    287        public boolean isClippedToWindow() {
    288            return true;
    289        }
    290
    291        /**
    292         * Draws the background to the specified graphics device.  If the dial
    293         * frame specifies a window, the clipping region will already have been
    294         * set to this window before this method is called.
    295         *
    296         * @param g2  the graphics device (<code>null</code> not permitted).
    297         * @param plot  the plot (ignored here).
    298         * @param frame  the dial frame (ignored here).
    299         * @param view  the view rectangle (<code>null</code> not permitted).
    300         */
    301        public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame,
    302                Rectangle2D view) {
    303
    304            // work out the anchor point
    305            Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius,
    306                    this.radius);
    307            Arc2D arc = new Arc2D.Double(f, this.angle, 0.0, Arc2D.OPEN);
    308            Point2D pt = arc.getStartPoint();
    309            g2.setPaint(this.paint);
    310            g2.setFont(this.font);
    311            TextUtilities.drawAlignedString(this.label, g2, (float) pt.getX(),
    312                    (float) pt.getY(), this.anchor);
    313
    314        }
    315
    316        /**
    317         * Tests this instance for equality with an arbitrary object.
    318         *
    319         * @param obj  the object (<code>null</code> permitted).
    320         *
    321         * @return A boolean.
    322         */
    323        public boolean equals(Object obj) {
    324            if (obj == this) {
    325                return true;
    326            }
    327            if (!(obj instanceof DialTextAnnotation)) {
    328                return false;
    329            }
    330            DialTextAnnotation that = (DialTextAnnotation) obj;
    331            if (!this.label.equals(that.label)) {
    332                return false;
    333            }
    334            if (!this.font.equals(that.font)) {
    335                return false;
    336            }
    337            if (!PaintUtilities.equal(this.paint, that.paint)) {
    338                return false;
    339            }
    340            if (this.radius != that.radius) {
    341                return false;
    342            }
    343            if (this.angle != that.angle) {
    344                return false;
    345            }
    346            if (!this.anchor.equals(that.anchor)) {
    347                return false;
    348            }
    349            return super.equals(obj);
    350        }
    351
    352        /**
    353         * Returns a hash code for this instance.
    354         *
    355         * @return The hash code.
    356         */
    357        public int hashCode() {
    358            int result = 193;
    359            result = 37 * result + HashUtilities.hashCodeForPaint(this.paint);
    360            result = 37 * result + this.font.hashCode();
    361            result = 37 * result + this.label.hashCode();
    362            result = 37 * result + this.anchor.hashCode();
    363            long temp = Double.doubleToLongBits(this.angle);
    364            result = 37 * result + (int) (temp ^ (temp >>> 32));
    365            temp = Double.doubleToLongBits(this.radius);
    366            result = 37 * result + (int) (temp ^ (temp >>> 32));
    367            return result;
    368        }
    369
    370        /**
    371         * Returns a clone of this instance.
    372         *
    373         * @return The clone.
    374         *
    375         * @throws CloneNotSupportedException if some attribute of this instance
    376         *     cannot be cloned.
    377         */
    378        public Object clone() throws CloneNotSupportedException {
    379            return super.clone();
    380        }
    381
    382        /**
    383         * Provides serialization support.
    384         *
    385         * @param stream  the output stream.
    386         *
    387         * @throws IOException  if there is an I/O error.
    388         */
    389        private void writeObject(ObjectOutputStream stream) throws IOException {
    390            stream.defaultWriteObject();
    391            SerialUtilities.writePaint(this.paint, stream);
    392        }
    393
    394        /**
    395         * Provides serialization support.
    396         *
    397         * @param stream  the input stream.
    398         *
    399         * @throws IOException  if there is an I/O error.
    400         * @throws ClassNotFoundException  if there is a classpath problem.
    401         */
    402        private void readObject(ObjectInputStream stream)
    403                throws IOException, ClassNotFoundException {
    404            stream.defaultReadObject();
    405            this.paint = SerialUtilities.readPaint(stream);
    406        }
    407
    408    }