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