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