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
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}