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