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}