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 }