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