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