    001/* ===========================================================
    002 * JFreeChart : a free chart library for the Java(tm) platform
    003 * ===========================================================
    004 *
    005 * (C) Copyright 2000-2011, 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 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025 * Other names may be trademarks of their respective owners.]
    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
    044package org.jfree.chart.plot.dial;
    045
    046import java.awt.Color;
    047import java.awt.Font;
    048import java.awt.Graphics2D;
    049import java.awt.Paint;
    050import java.awt.geom.Arc2D;
    051import java.awt.geom.Point2D;
    052import java.awt.geom.Rectangle2D;
    053import java.io.IOException;
    054import java.io.ObjectInputStream;
    055import java.io.ObjectOutputStream;
    056import java.io.Serializable;
    057
    058import org.jfree.chart.HashUtilities;
    059import org.jfree.io.SerialUtilities;
    060import org.jfree.text.TextUtilities;
    061import org.jfree.ui.TextAnchor;
    062import org.jfree.util.PaintUtilities;
    063import org.jfree.util.PublicCloneable;
    064
    065/**
    066 * A text annotation for a {@link DialPlot}.
    067 *
    068 * @since 1.0.7
    069 */
    070public 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}