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 * XYTextAnnotation.java 029 * --------------------- 030 * (C) Copyright 2002-2008, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes: 036 * -------- 037 * 28-Aug-2002 : Version 1 (DG); 038 * 07-Nov-2002 : Fixed errors reported by Checkstyle (DG); 039 * 13-Jan-2003 : Reviewed Javadocs (DG); 040 * 26-Mar-2003 : Implemented Serializable (DG); 041 * 02-Jul-2003 : Added new text alignment and rotation options (DG); 042 * 19-Aug-2003 : Implemented Cloneable (DG); 043 * 17-Jan-2003 : Added fix for bug 878706, where the annotation is placed 044 * incorrectly for a plot with horizontal orientation (thanks to 045 * Ed Yu for the fix) (DG); 046 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 047 * ------------- JFREECHART 1.0.x --------------------------------------------- 048 * 26-Jan-2006 : Fixed equals() method (bug 1415480) (DG); 049 * 06-Mar-2007 : Added argument checks, re-implemented hashCode() method (DG); 050 * 051 */ 052 053 package org.jfree.chart.annotations; 054 055 import java.awt.Color; 056 import java.awt.Font; 057 import java.awt.Graphics2D; 058 import java.awt.Paint; 059 import java.awt.Shape; 060 import java.awt.geom.Rectangle2D; 061 import java.io.IOException; 062 import java.io.ObjectInputStream; 063 import java.io.ObjectOutputStream; 064 import java.io.Serializable; 065 066 import org.jfree.chart.HashUtilities; 067 import org.jfree.chart.axis.ValueAxis; 068 import org.jfree.chart.plot.Plot; 069 import org.jfree.chart.plot.PlotOrientation; 070 import org.jfree.chart.plot.PlotRenderingInfo; 071 import org.jfree.chart.plot.XYPlot; 072 import org.jfree.io.SerialUtilities; 073 import org.jfree.text.TextUtilities; 074 import org.jfree.ui.RectangleEdge; 075 import org.jfree.ui.TextAnchor; 076 import org.jfree.util.PaintUtilities; 077 import org.jfree.util.PublicCloneable; 078 079 /** 080 * A text annotation that can be placed at a particular (x, y) location on an 081 * {@link XYPlot}. 082 */ 083 public class XYTextAnnotation extends AbstractXYAnnotation 084 implements Cloneable, PublicCloneable, Serializable { 085 086 /** For serialization. */ 087 private static final long serialVersionUID = -2946063342782506328L; 088 089 /** The default font. */ 090 public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 091 10); 092 093 /** The default paint. */ 094 public static final Paint DEFAULT_PAINT = Color.black; 095 096 /** The default text anchor. */ 097 public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; 098 099 /** The default rotation anchor. */ 100 public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; 101 102 /** The default rotation angle. */ 103 public static final double DEFAULT_ROTATION_ANGLE = 0.0; 104 105 /** The text. */ 106 private String text; 107 108 /** The font. */ 109 private Font font; 110 111 /** The paint. */ 112 private transient Paint paint; 113 114 /** The x-coordinate. */ 115 private double x; 116 117 /** The y-coordinate. */ 118 private double y; 119 120 /** The text anchor (to be aligned with (x, y)). */ 121 private TextAnchor textAnchor; 122 123 /** The rotation anchor. */ 124 private TextAnchor rotationAnchor; 125 126 /** The rotation angle. */ 127 private double rotationAngle; 128 129 /** 130 * Creates a new annotation to be displayed at the given coordinates. The 131 * coordinates are specified in data space (they will be converted to 132 * Java2D space for display). 133 * 134 * @param text the text (<code>null</code> not permitted). 135 * @param x the x-coordinate (in data space). 136 * @param y the y-coordinate (in data space). 137 */ 138 public XYTextAnnotation(String text, double x, double y) { 139 if (text == null) { 140 throw new IllegalArgumentException("Null 'text' argument."); 141 } 142 this.text = text; 143 this.font = DEFAULT_FONT; 144 this.paint = DEFAULT_PAINT; 145 this.x = x; 146 this.y = y; 147 this.textAnchor = DEFAULT_TEXT_ANCHOR; 148 this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; 149 this.rotationAngle = DEFAULT_ROTATION_ANGLE; 150 } 151 152 /** 153 * Returns the text for the annotation. 154 * 155 * @return The text (never <code>null</code>). 156 * 157 * @see #setText(String) 158 */ 159 public String getText() { 160 return this.text; 161 } 162 163 /** 164 * Sets the text for the annotation. 165 * 166 * @param text the text (<code>null</code> not permitted). 167 * 168 * @see #getText() 169 */ 170 public void setText(String text) { 171 if (text == null) { 172 throw new IllegalArgumentException("Null 'text' argument."); 173 } 174 this.text = text; 175 } 176 177 /** 178 * Returns the font for the annotation. 179 * 180 * @return The font (never <code>null</code>). 181 * 182 * @see #setFont(Font) 183 */ 184 public Font getFont() { 185 return this.font; 186 } 187 188 /** 189 * Sets the font for the annotation. 190 * 191 * @param font the font (<code>null</code> not permitted). 192 * 193 * @see #getFont() 194 */ 195 public void setFont(Font font) { 196 if (font == null) { 197 throw new IllegalArgumentException("Null 'font' argument."); 198 } 199 this.font = font; 200 } 201 202 /** 203 * Returns the paint for the annotation. 204 * 205 * @return The paint (never <code>null</code>). 206 * 207 * @see #setPaint(Paint) 208 */ 209 public Paint getPaint() { 210 return this.paint; 211 } 212 213 /** 214 * Sets the paint for the annotation. 215 * 216 * @param paint the paint (<code>null</code> not permitted). 217 * 218 * @see #getPaint() 219 */ 220 public void setPaint(Paint paint) { 221 if (paint == null) { 222 throw new IllegalArgumentException("Null 'paint' argument."); 223 } 224 this.paint = paint; 225 } 226 227 /** 228 * Returns the text anchor. 229 * 230 * @return The text anchor (never <code>null</code>). 231 * 232 * @see #setTextAnchor(TextAnchor) 233 */ 234 public TextAnchor getTextAnchor() { 235 return this.textAnchor; 236 } 237 238 /** 239 * Sets the text anchor (the point on the text bounding rectangle that is 240 * aligned to the (x, y) coordinate of the annotation). 241 * 242 * @param anchor the anchor point (<code>null</code> not permitted). 243 * 244 * @see #getTextAnchor() 245 */ 246 public void setTextAnchor(TextAnchor anchor) { 247 if (anchor == null) { 248 throw new IllegalArgumentException("Null 'anchor' argument."); 249 } 250 this.textAnchor = anchor; 251 } 252 253 /** 254 * Returns the rotation anchor. 255 * 256 * @return The rotation anchor point (never <code>null</code>). 257 * 258 * @see #setRotationAnchor(TextAnchor) 259 */ 260 public TextAnchor getRotationAnchor() { 261 return this.rotationAnchor; 262 } 263 264 /** 265 * Sets the rotation anchor point. 266 * 267 * @param anchor the anchor (<code>null</code> not permitted). 268 * 269 * @see #getRotationAnchor() 270 */ 271 public void setRotationAnchor(TextAnchor anchor) { 272 if (anchor == null) { 273 throw new IllegalArgumentException("Null 'anchor' argument."); 274 } 275 this.rotationAnchor = anchor; 276 } 277 278 /** 279 * Returns the rotation angle. 280 * 281 * @return The rotation angle. 282 * 283 * @see #setRotationAngle(double) 284 */ 285 public double getRotationAngle() { 286 return this.rotationAngle; 287 } 288 289 /** 290 * Sets the rotation angle. The angle is measured clockwise in radians. 291 * 292 * @param angle the angle (in radians). 293 * 294 * @see #getRotationAngle() 295 */ 296 public void setRotationAngle(double angle) { 297 this.rotationAngle = angle; 298 } 299 300 /** 301 * Returns the x coordinate for the text anchor point (measured against the 302 * domain axis). 303 * 304 * @return The x coordinate (in data space). 305 * 306 * @see #setX(double) 307 */ 308 public double getX() { 309 return this.x; 310 } 311 312 /** 313 * Sets the x coordinate for the text anchor point (measured against the 314 * domain axis). 315 * 316 * @param x the x coordinate (in data space). 317 * 318 * @see #getX() 319 */ 320 public void setX(double x) { 321 this.x = x; 322 } 323 324 /** 325 * Returns the y coordinate for the text anchor point (measured against the 326 * range axis). 327 * 328 * @return The y coordinate (in data space). 329 * 330 * @see #setY(double) 331 */ 332 public double getY() { 333 return this.y; 334 } 335 336 /** 337 * Sets the y coordinate for the text anchor point (measured against the 338 * range axis). 339 * 340 * @param y the y coordinate. 341 * 342 * @see #getY() 343 */ 344 public void setY(double y) { 345 this.y = y; 346 } 347 348 /** 349 * Draws the annotation. 350 * 351 * @param g2 the graphics device. 352 * @param plot the plot. 353 * @param dataArea the data area. 354 * @param domainAxis the domain axis. 355 * @param rangeAxis the range axis. 356 * @param rendererIndex the renderer index. 357 * @param info an optional info object that will be populated with 358 * entity information. 359 */ 360 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 361 ValueAxis domainAxis, ValueAxis rangeAxis, 362 int rendererIndex, 363 PlotRenderingInfo info) { 364 365 PlotOrientation orientation = plot.getOrientation(); 366 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 367 plot.getDomainAxisLocation(), orientation); 368 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 369 plot.getRangeAxisLocation(), orientation); 370 371 float anchorX = (float) domainAxis.valueToJava2D( 372 this.x, dataArea, domainEdge); 373 float anchorY = (float) rangeAxis.valueToJava2D( 374 this.y, dataArea, rangeEdge); 375 376 if (orientation == PlotOrientation.HORIZONTAL) { 377 float tempAnchor = anchorX; 378 anchorX = anchorY; 379 anchorY = tempAnchor; 380 } 381 382 g2.setFont(getFont()); 383 g2.setPaint(getPaint()); 384 TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY, 385 getTextAnchor(), getRotationAngle(), getRotationAnchor()); 386 Shape hotspot = TextUtilities.calculateRotatedStringBounds( 387 getText(), g2, anchorX, anchorY, getTextAnchor(), 388 getRotationAngle(), getRotationAnchor()); 389 390 String toolTip = getToolTipText(); 391 String url = getURL(); 392 if (toolTip != null || url != null) { 393 addEntity(info, hotspot, rendererIndex, toolTip, url); 394 } 395 396 } 397 398 /** 399 * Tests this annotation for equality with an arbitrary object. 400 * 401 * @param obj the object (<code>null</code> permitted). 402 * 403 * @return A boolean. 404 */ 405 public boolean equals(Object obj) { 406 if (obj == this) { 407 return true; 408 } 409 if (!(obj instanceof XYTextAnnotation)) { 410 return false; 411 } 412 if (!super.equals(obj)) { 413 return false; 414 } 415 XYTextAnnotation that = (XYTextAnnotation) obj; 416 if (!this.text.equals(that.text)) { 417 return false; 418 } 419 if (this.x != that.x) { 420 return false; 421 } 422 if (this.y != that.y) { 423 return false; 424 } 425 if (!this.font.equals(that.font)) { 426 return false; 427 } 428 if (!PaintUtilities.equal(this.paint, that.paint)) { 429 return false; 430 } 431 if (!this.rotationAnchor.equals(that.rotationAnchor)) { 432 return false; 433 } 434 if (this.rotationAngle != that.rotationAngle) { 435 return false; 436 } 437 if (!this.textAnchor.equals(that.textAnchor)) { 438 return false; 439 } 440 return true; 441 } 442 443 /** 444 * Returns a hash code for the object. 445 * 446 * @return A hash code. 447 */ 448 public int hashCode() { 449 int result = 193; 450 result = 37 * this.text.hashCode(); 451 result = 37 * this.font.hashCode(); 452 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint); 453 long temp = Double.doubleToLongBits(this.x); 454 result = 37 * result + (int) (temp ^ (temp >>> 32)); 455 temp = Double.doubleToLongBits(this.y); 456 result = 37 * result + (int) (temp ^ (temp >>> 32)); 457 result = 37 * result + this.textAnchor.hashCode(); 458 result = 37 * result + this.rotationAnchor.hashCode(); 459 temp = Double.doubleToLongBits(this.rotationAngle); 460 result = 37 * result + (int) (temp ^ (temp >>> 32)); 461 return result; 462 } 463 464 /** 465 * Returns a clone of the annotation. 466 * 467 * @return A clone. 468 * 469 * @throws CloneNotSupportedException if the annotation can't be cloned. 470 */ 471 public Object clone() throws CloneNotSupportedException { 472 return super.clone(); 473 } 474 475 /** 476 * Provides serialization support. 477 * 478 * @param stream the output stream. 479 * 480 * @throws IOException if there is an I/O error. 481 */ 482 private void writeObject(ObjectOutputStream stream) throws IOException { 483 stream.defaultWriteObject(); 484 SerialUtilities.writePaint(this.paint, stream); 485 } 486 487 /** 488 * Provides serialization support. 489 * 490 * @param stream the input stream. 491 * 492 * @throws IOException if there is an I/O error. 493 * @throws ClassNotFoundException if there is a classpath problem. 494 */ 495 private void readObject(ObjectInputStream stream) 496 throws IOException, ClassNotFoundException { 497 stream.defaultReadObject(); 498 this.paint = SerialUtilities.readPaint(stream); 499 } 500 501 }