001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2009, 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-2009, 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 * 12-Feb-2009 : Added background paint and outline paint/stroke (DG); 051 * 01-Apr-2009 : Fixed bug in hotspot calculation (DG); 052 * 053 */ 054 055 package org.jfree.chart.annotations; 056 057 import java.awt.BasicStroke; 058 import java.awt.Color; 059 import java.awt.Font; 060 import java.awt.Graphics2D; 061 import java.awt.Paint; 062 import java.awt.Shape; 063 import java.awt.Stroke; 064 import java.awt.geom.Rectangle2D; 065 import java.io.IOException; 066 import java.io.ObjectInputStream; 067 import java.io.ObjectOutputStream; 068 import java.io.Serializable; 069 070 import org.jfree.chart.HashUtilities; 071 import org.jfree.chart.axis.ValueAxis; 072 import org.jfree.chart.plot.Plot; 073 import org.jfree.chart.plot.PlotOrientation; 074 import org.jfree.chart.plot.PlotRenderingInfo; 075 import org.jfree.chart.plot.XYPlot; 076 import org.jfree.io.SerialUtilities; 077 import org.jfree.text.TextUtilities; 078 import org.jfree.ui.RectangleEdge; 079 import org.jfree.ui.TextAnchor; 080 import org.jfree.util.PaintUtilities; 081 import org.jfree.util.PublicCloneable; 082 083 /** 084 * A text annotation that can be placed at a particular (x, y) location on an 085 * {@link XYPlot}. 086 */ 087 public class XYTextAnnotation extends AbstractXYAnnotation 088 implements Cloneable, PublicCloneable, Serializable { 089 090 /** For serialization. */ 091 private static final long serialVersionUID = -2946063342782506328L; 092 093 /** The default font. */ 094 public static final Font DEFAULT_FONT = new Font("SansSerif", Font.PLAIN, 095 10); 096 097 /** The default paint. */ 098 public static final Paint DEFAULT_PAINT = Color.black; 099 100 /** The default text anchor. */ 101 public static final TextAnchor DEFAULT_TEXT_ANCHOR = TextAnchor.CENTER; 102 103 /** The default rotation anchor. */ 104 public static final TextAnchor DEFAULT_ROTATION_ANCHOR = TextAnchor.CENTER; 105 106 /** The default rotation angle. */ 107 public static final double DEFAULT_ROTATION_ANGLE = 0.0; 108 109 /** The text. */ 110 private String text; 111 112 /** The font. */ 113 private Font font; 114 115 /** The paint. */ 116 private transient Paint paint; 117 118 /** The x-coordinate. */ 119 private double x; 120 121 /** The y-coordinate. */ 122 private double y; 123 124 /** The text anchor (to be aligned with (x, y)). */ 125 private TextAnchor textAnchor; 126 127 /** The rotation anchor. */ 128 private TextAnchor rotationAnchor; 129 130 /** The rotation angle. */ 131 private double rotationAngle; 132 133 /** 134 * The background paint (possibly null). 135 * 136 * @since 1.0.13 137 */ 138 private transient Paint backgroundPaint; 139 140 /** 141 * The flag that controls the visibility of the outline. 142 * 143 * @since 1.0.13 144 */ 145 private boolean outlineVisible; 146 147 /** 148 * The outline paint (never null). 149 * 150 * @since 1.0.13 151 */ 152 private transient Paint outlinePaint; 153 154 /** 155 * The outline stroke (never null). 156 * 157 * @since 1.0.13 158 */ 159 private transient Stroke outlineStroke; 160 161 /** 162 * Creates a new annotation to be displayed at the given coordinates. The 163 * coordinates are specified in data space (they will be converted to 164 * Java2D space for display). 165 * 166 * @param text the text (<code>null</code> not permitted). 167 * @param x the x-coordinate (in data space). 168 * @param y the y-coordinate (in data space). 169 */ 170 public XYTextAnnotation(String text, double x, double y) { 171 if (text == null) { 172 throw new IllegalArgumentException("Null 'text' argument."); 173 } 174 this.text = text; 175 this.font = DEFAULT_FONT; 176 this.paint = DEFAULT_PAINT; 177 this.x = x; 178 this.y = y; 179 this.textAnchor = DEFAULT_TEXT_ANCHOR; 180 this.rotationAnchor = DEFAULT_ROTATION_ANCHOR; 181 this.rotationAngle = DEFAULT_ROTATION_ANGLE; 182 183 // by default the outline and background won't be visible 184 this.backgroundPaint = null; 185 this.outlineVisible = false; 186 this.outlinePaint = Color.black; 187 this.outlineStroke = new BasicStroke(0.5f); 188 } 189 190 /** 191 * Returns the text for the annotation. 192 * 193 * @return The text (never <code>null</code>). 194 * 195 * @see #setText(String) 196 */ 197 public String getText() { 198 return this.text; 199 } 200 201 /** 202 * Sets the text for the annotation. 203 * 204 * @param text the text (<code>null</code> not permitted). 205 * 206 * @see #getText() 207 */ 208 public void setText(String text) { 209 if (text == null) { 210 throw new IllegalArgumentException("Null 'text' argument."); 211 } 212 this.text = text; 213 } 214 215 /** 216 * Returns the font for the annotation. 217 * 218 * @return The font (never <code>null</code>). 219 * 220 * @see #setFont(Font) 221 */ 222 public Font getFont() { 223 return this.font; 224 } 225 226 /** 227 * Sets the font for the annotation. 228 * 229 * @param font the font (<code>null</code> not permitted). 230 * 231 * @see #getFont() 232 */ 233 public void setFont(Font font) { 234 if (font == null) { 235 throw new IllegalArgumentException("Null 'font' argument."); 236 } 237 this.font = font; 238 } 239 240 /** 241 * Returns the paint for the annotation. 242 * 243 * @return The paint (never <code>null</code>). 244 * 245 * @see #setPaint(Paint) 246 */ 247 public Paint getPaint() { 248 return this.paint; 249 } 250 251 /** 252 * Sets the paint for the annotation. 253 * 254 * @param paint the paint (<code>null</code> not permitted). 255 * 256 * @see #getPaint() 257 */ 258 public void setPaint(Paint paint) { 259 if (paint == null) { 260 throw new IllegalArgumentException("Null 'paint' argument."); 261 } 262 this.paint = paint; 263 } 264 265 /** 266 * Returns the text anchor. 267 * 268 * @return The text anchor (never <code>null</code>). 269 * 270 * @see #setTextAnchor(TextAnchor) 271 */ 272 public TextAnchor getTextAnchor() { 273 return this.textAnchor; 274 } 275 276 /** 277 * Sets the text anchor (the point on the text bounding rectangle that is 278 * aligned to the (x, y) coordinate of the annotation). 279 * 280 * @param anchor the anchor point (<code>null</code> not permitted). 281 * 282 * @see #getTextAnchor() 283 */ 284 public void setTextAnchor(TextAnchor anchor) { 285 if (anchor == null) { 286 throw new IllegalArgumentException("Null 'anchor' argument."); 287 } 288 this.textAnchor = anchor; 289 } 290 291 /** 292 * Returns the rotation anchor. 293 * 294 * @return The rotation anchor point (never <code>null</code>). 295 * 296 * @see #setRotationAnchor(TextAnchor) 297 */ 298 public TextAnchor getRotationAnchor() { 299 return this.rotationAnchor; 300 } 301 302 /** 303 * Sets the rotation anchor point. 304 * 305 * @param anchor the anchor (<code>null</code> not permitted). 306 * 307 * @see #getRotationAnchor() 308 */ 309 public void setRotationAnchor(TextAnchor anchor) { 310 if (anchor == null) { 311 throw new IllegalArgumentException("Null 'anchor' argument."); 312 } 313 this.rotationAnchor = anchor; 314 } 315 316 /** 317 * Returns the rotation angle. 318 * 319 * @return The rotation angle. 320 * 321 * @see #setRotationAngle(double) 322 */ 323 public double getRotationAngle() { 324 return this.rotationAngle; 325 } 326 327 /** 328 * Sets the rotation angle. The angle is measured clockwise in radians. 329 * 330 * @param angle the angle (in radians). 331 * 332 * @see #getRotationAngle() 333 */ 334 public void setRotationAngle(double angle) { 335 this.rotationAngle = angle; 336 } 337 338 /** 339 * Returns the x coordinate for the text anchor point (measured against the 340 * domain axis). 341 * 342 * @return The x coordinate (in data space). 343 * 344 * @see #setX(double) 345 */ 346 public double getX() { 347 return this.x; 348 } 349 350 /** 351 * Sets the x coordinate for the text anchor point (measured against the 352 * domain axis). 353 * 354 * @param x the x coordinate (in data space). 355 * 356 * @see #getX() 357 */ 358 public void setX(double x) { 359 this.x = x; 360 } 361 362 /** 363 * Returns the y coordinate for the text anchor point (measured against the 364 * range axis). 365 * 366 * @return The y coordinate (in data space). 367 * 368 * @see #setY(double) 369 */ 370 public double getY() { 371 return this.y; 372 } 373 374 /** 375 * Sets the y coordinate for the text anchor point (measured against the 376 * range axis). 377 * 378 * @param y the y coordinate. 379 * 380 * @see #getY() 381 */ 382 public void setY(double y) { 383 this.y = y; 384 } 385 386 /** 387 * Returns the background paint for the annotation. 388 * 389 * @return The background paint (possibly <code>null</code>). 390 * 391 * @see #setBackgroundPaint(Paint) 392 * 393 * @since 1.0.13 394 */ 395 public Paint getBackgroundPaint() { 396 return this.backgroundPaint; 397 } 398 399 /** 400 * Sets the background paint for the annotation. 401 * 402 * @param paint the paint (<code>null</code> permitted). 403 * 404 * @see #getBackgroundPaint() 405 * 406 * @since 1.0.13 407 */ 408 public void setBackgroundPaint(Paint paint) { 409 this.backgroundPaint = paint; 410 } 411 412 /** 413 * Returns the outline paint for the annotation. 414 * 415 * @return The outline paint (never <code>null</code>). 416 * 417 * @see #setOutlinePaint(Paint) 418 * 419 * @since 1.0.13 420 */ 421 public Paint getOutlinePaint() { 422 return this.outlinePaint; 423 } 424 425 /** 426 * Sets the outline paint for the annotation. 427 * 428 * @param paint the paint (<code>null</code> not permitted). 429 * 430 * @see #getOutlinePaint() 431 * 432 * @since 1.0.13 433 */ 434 public void setOutlinePaint(Paint paint) { 435 if (paint == null) { 436 throw new IllegalArgumentException("Null 'paint' argument."); 437 } 438 this.outlinePaint = paint; 439 } 440 441 /** 442 * Returns the outline stroke for the annotation. 443 * 444 * @return The outline stroke (never <code>null</code>). 445 * 446 * @see #setOutlineStroke(Stroke) 447 * 448 * @since 1.0.13 449 */ 450 public Stroke getOutlineStroke() { 451 return this.outlineStroke; 452 } 453 454 /** 455 * Sets the outline stroke for the annotation. 456 * 457 * @param stroke the stroke (<code>null</code> not permitted). 458 * 459 * @see #getOutlineStroke() 460 * 461 * @since 1.0.13 462 */ 463 public void setOutlineStroke(Stroke stroke) { 464 if (stroke == null) { 465 throw new IllegalArgumentException("Null 'stroke' argument."); 466 } 467 this.outlineStroke = stroke; 468 } 469 470 /** 471 * Returns the flag that controls whether or not the outline is drawn. 472 * 473 * @return A boolean. 474 * 475 * @since 1.0.13 476 */ 477 public boolean isOutlineVisible() { 478 return this.outlineVisible; 479 } 480 481 /** 482 * Sets the flag that controls whether or not the outline is drawn. 483 * 484 * @param visible the new flag value. 485 * 486 * @since 1.0.13 487 */ 488 public void setOutlineVisible(boolean visible) { 489 this.outlineVisible = visible; 490 } 491 492 /** 493 * Draws the annotation. 494 * 495 * @param g2 the graphics device. 496 * @param plot the plot. 497 * @param dataArea the data area. 498 * @param domainAxis the domain axis. 499 * @param rangeAxis the range axis. 500 * @param rendererIndex the renderer index. 501 * @param info an optional info object that will be populated with 502 * entity information. 503 */ 504 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 505 ValueAxis domainAxis, ValueAxis rangeAxis, 506 int rendererIndex, 507 PlotRenderingInfo info) { 508 509 PlotOrientation orientation = plot.getOrientation(); 510 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 511 plot.getDomainAxisLocation(), orientation); 512 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 513 plot.getRangeAxisLocation(), orientation); 514 515 float anchorX = (float) domainAxis.valueToJava2D( 516 this.x, dataArea, domainEdge); 517 float anchorY = (float) rangeAxis.valueToJava2D( 518 this.y, dataArea, rangeEdge); 519 520 if (orientation == PlotOrientation.HORIZONTAL) { 521 float tempAnchor = anchorX; 522 anchorX = anchorY; 523 anchorY = tempAnchor; 524 } 525 526 g2.setFont(getFont()); 527 Shape hotspot = TextUtilities.calculateRotatedStringBounds( 528 getText(), g2, anchorX, anchorY, getTextAnchor(), 529 getRotationAngle(), getRotationAnchor()); 530 if (this.backgroundPaint != null) { 531 g2.setPaint(this.backgroundPaint); 532 g2.fill(hotspot); 533 } 534 g2.setPaint(getPaint()); 535 TextUtilities.drawRotatedString(getText(), g2, anchorX, anchorY, 536 getTextAnchor(), getRotationAngle(), getRotationAnchor()); 537 if (this.outlineVisible) { 538 g2.setStroke(this.outlineStroke); 539 g2.setPaint(this.outlinePaint); 540 g2.draw(hotspot); 541 } 542 543 String toolTip = getToolTipText(); 544 String url = getURL(); 545 if (toolTip != null || url != null) { 546 addEntity(info, hotspot, rendererIndex, toolTip, url); 547 } 548 549 } 550 551 /** 552 * Tests this annotation for equality with an arbitrary object. 553 * 554 * @param obj the object (<code>null</code> permitted). 555 * 556 * @return A boolean. 557 */ 558 public boolean equals(Object obj) { 559 if (obj == this) { 560 return true; 561 } 562 if (!(obj instanceof XYTextAnnotation)) { 563 return false; 564 } 565 XYTextAnnotation that = (XYTextAnnotation) obj; 566 if (!this.text.equals(that.text)) { 567 return false; 568 } 569 if (this.x != that.x) { 570 return false; 571 } 572 if (this.y != that.y) { 573 return false; 574 } 575 if (!this.font.equals(that.font)) { 576 return false; 577 } 578 if (!PaintUtilities.equal(this.paint, that.paint)) { 579 return false; 580 } 581 if (!this.rotationAnchor.equals(that.rotationAnchor)) { 582 return false; 583 } 584 if (this.rotationAngle != that.rotationAngle) { 585 return false; 586 } 587 if (!this.textAnchor.equals(that.textAnchor)) { 588 return false; 589 } 590 if (this.outlineVisible != that.outlineVisible) { 591 return false; 592 } 593 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 594 return false; 595 } 596 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) { 597 return false; 598 } 599 if (!(this.outlineStroke.equals(that.outlineStroke))) { 600 return false; 601 } 602 return super.equals(obj); 603 } 604 605 /** 606 * Returns a hash code for the object. 607 * 608 * @return A hash code. 609 */ 610 public int hashCode() { 611 int result = 193; 612 result = 37 * this.text.hashCode(); 613 result = 37 * this.font.hashCode(); 614 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint); 615 long temp = Double.doubleToLongBits(this.x); 616 result = 37 * result + (int) (temp ^ (temp >>> 32)); 617 temp = Double.doubleToLongBits(this.y); 618 result = 37 * result + (int) (temp ^ (temp >>> 32)); 619 result = 37 * result + this.textAnchor.hashCode(); 620 result = 37 * result + this.rotationAnchor.hashCode(); 621 temp = Double.doubleToLongBits(this.rotationAngle); 622 result = 37 * result + (int) (temp ^ (temp >>> 32)); 623 return result; 624 } 625 626 /** 627 * Returns a clone of the annotation. 628 * 629 * @return A clone. 630 * 631 * @throws CloneNotSupportedException if the annotation can't be cloned. 632 */ 633 public Object clone() throws CloneNotSupportedException { 634 return super.clone(); 635 } 636 637 /** 638 * Provides serialization support. 639 * 640 * @param stream the output stream. 641 * 642 * @throws IOException if there is an I/O error. 643 */ 644 private void writeObject(ObjectOutputStream stream) throws IOException { 645 stream.defaultWriteObject(); 646 SerialUtilities.writePaint(this.paint, stream); 647 SerialUtilities.writePaint(this.backgroundPaint, stream); 648 SerialUtilities.writePaint(this.outlinePaint, stream); 649 SerialUtilities.writeStroke(this.outlineStroke, stream); 650 } 651 652 /** 653 * Provides serialization support. 654 * 655 * @param stream the input stream. 656 * 657 * @throws IOException if there is an I/O error. 658 * @throws ClassNotFoundException if there is a classpath problem. 659 */ 660 private void readObject(ObjectInputStream stream) 661 throws IOException, ClassNotFoundException { 662 stream.defaultReadObject(); 663 this.paint = SerialUtilities.readPaint(stream); 664 this.backgroundPaint = SerialUtilities.readPaint(stream); 665 this.outlinePaint = SerialUtilities.readPaint(stream); 666 this.outlineStroke = SerialUtilities.readStroke(stream); 667 } 668 669 }