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