001/* ====================================================== 002 * JFreeChart : a chart library for the Java(tm) platform 003 * ====================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: https://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 * CrosshairOverlay.java 029 * --------------------- 030 * (C) Copyright 2011-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): John Matthews, Michal Wozniak; 034 * 035 */ 036 037package org.jfree.chart.panel; 038 039import java.awt.Font; 040import java.awt.Graphics2D; 041import java.awt.Paint; 042import java.awt.Rectangle; 043import java.awt.Shape; 044import java.awt.Stroke; 045import java.awt.geom.Line2D; 046import java.awt.geom.Point2D; 047import java.awt.geom.Rectangle2D; 048import java.beans.PropertyChangeEvent; 049import java.beans.PropertyChangeListener; 050import java.io.Serializable; 051import java.util.ArrayList; 052import java.util.List; 053import org.jfree.chart.ChartPanel; 054import org.jfree.chart.JFreeChart; 055import org.jfree.chart.axis.ValueAxis; 056import org.jfree.chart.event.OverlayChangeEvent; 057import org.jfree.chart.plot.Crosshair; 058import org.jfree.chart.plot.PlotOrientation; 059import org.jfree.chart.plot.XYPlot; 060import org.jfree.chart.text.TextUtils; 061import org.jfree.chart.ui.RectangleAnchor; 062import org.jfree.chart.ui.RectangleEdge; 063import org.jfree.chart.ui.RectangleInsets; 064import org.jfree.chart.ui.TextAnchor; 065import org.jfree.chart.util.ObjectUtils; 066import org.jfree.chart.util.Args; 067import org.jfree.chart.util.PublicCloneable; 068 069/** 070 * An overlay for a {@link ChartPanel} that draws crosshairs on a chart. If 071 * you are using the JavaFX extensions for JFreeChart, then you should use 072 * the {@code CrosshairOverlayFX} class. 073 */ 074public class CrosshairOverlay extends AbstractOverlay implements Overlay, 075 PropertyChangeListener, PublicCloneable, Cloneable, Serializable { 076 077 /** Storage for the crosshairs along the x-axis. */ 078 private List<Crosshair> xCrosshairs; 079 080 /** Storage for the crosshairs along the y-axis. */ 081 private List<Crosshair> yCrosshairs; 082 083 /** 084 * Creates a new overlay that initially contains no crosshairs. 085 */ 086 public CrosshairOverlay() { 087 super(); 088 this.xCrosshairs = new ArrayList<>(); 089 this.yCrosshairs = new ArrayList<>(); 090 } 091 092 /** 093 * Adds a crosshair against the domain axis (x-axis) and sends an 094 * {@link OverlayChangeEvent} to all registered listeners. 095 * 096 * @param crosshair the crosshair ({@code null} not permitted). 097 * 098 * @see #removeDomainCrosshair(org.jfree.chart.plot.Crosshair) 099 * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) 100 */ 101 public void addDomainCrosshair(Crosshair crosshair) { 102 Args.nullNotPermitted(crosshair, "crosshair"); 103 this.xCrosshairs.add(crosshair); 104 crosshair.addPropertyChangeListener(this); 105 fireOverlayChanged(); 106 } 107 108 /** 109 * Removes a domain axis crosshair and sends an {@link OverlayChangeEvent} 110 * to all registered listeners. 111 * 112 * @param crosshair the crosshair ({@code null} not permitted). 113 * 114 * @see #addDomainCrosshair(org.jfree.chart.plot.Crosshair) 115 */ 116 public void removeDomainCrosshair(Crosshair crosshair) { 117 Args.nullNotPermitted(crosshair, "crosshair"); 118 if (this.xCrosshairs.remove(crosshair)) { 119 crosshair.removePropertyChangeListener(this); 120 fireOverlayChanged(); 121 } 122 } 123 124 /** 125 * Clears all the domain crosshairs from the overlay and sends an 126 * {@link OverlayChangeEvent} to all registered listeners (unless there 127 * were no crosshairs to begin with). 128 */ 129 public void clearDomainCrosshairs() { 130 if (this.xCrosshairs.isEmpty()) { 131 return; // nothing to do - avoids firing change event 132 } 133 for (Crosshair c : getDomainCrosshairs()) { 134 this.xCrosshairs.remove(c); 135 c.removePropertyChangeListener(this); 136 } 137 fireOverlayChanged(); 138 } 139 140 /** 141 * Returns a new list containing the domain crosshairs for this overlay. 142 * 143 * @return A list of crosshairs. 144 */ 145 public List<Crosshair> getDomainCrosshairs() { 146 return new ArrayList<>(this.xCrosshairs); 147 } 148 149 /** 150 * Adds a crosshair against the range axis and sends an 151 * {@link OverlayChangeEvent} to all registered listeners. 152 * 153 * @param crosshair the crosshair ({@code null} not permitted). 154 */ 155 public void addRangeCrosshair(Crosshair crosshair) { 156 Args.nullNotPermitted(crosshair, "crosshair"); 157 this.yCrosshairs.add(crosshair); 158 crosshair.addPropertyChangeListener(this); 159 fireOverlayChanged(); 160 } 161 162 /** 163 * Removes a range axis crosshair and sends an {@link OverlayChangeEvent} 164 * to all registered listeners. 165 * 166 * @param crosshair the crosshair ({@code null} not permitted). 167 * 168 * @see #addRangeCrosshair(org.jfree.chart.plot.Crosshair) 169 */ 170 public void removeRangeCrosshair(Crosshair crosshair) { 171 Args.nullNotPermitted(crosshair, "crosshair"); 172 if (this.yCrosshairs.remove(crosshair)) { 173 crosshair.removePropertyChangeListener(this); 174 fireOverlayChanged(); 175 } 176 } 177 178 /** 179 * Clears all the range crosshairs from the overlay and sends an 180 * {@link OverlayChangeEvent} to all registered listeners (unless there 181 * were no crosshairs to begin with). 182 */ 183 public void clearRangeCrosshairs() { 184 if (this.yCrosshairs.isEmpty()) { 185 return; // nothing to do - avoids change notification 186 } 187 for (Crosshair c : getRangeCrosshairs()) { 188 this.yCrosshairs.remove(c); 189 c.removePropertyChangeListener(this); 190 } 191 fireOverlayChanged(); 192 } 193 194 /** 195 * Returns a new list containing the range crosshairs for this overlay. 196 * 197 * @return A list of crosshairs. 198 */ 199 public List<Crosshair> getRangeCrosshairs() { 200 return new ArrayList<>(this.yCrosshairs); 201 } 202 203 /** 204 * Receives a property change event (typically a change in one of the 205 * crosshairs). 206 * 207 * @param e the event. 208 */ 209 @Override 210 public void propertyChange(PropertyChangeEvent e) { 211 fireOverlayChanged(); 212 } 213 214 /** 215 * Renders the crosshairs in the overlay on top of the chart that has just 216 * been rendered in the specified {@code chartPanel}. This method is 217 * called by the JFreeChart framework, you won't normally call it from 218 * user code. 219 * 220 * @param g2 the graphics target. 221 * @param chartPanel the chart panel. 222 */ 223 @Override 224 public void paintOverlay(Graphics2D g2, ChartPanel chartPanel) { 225 Shape savedClip = g2.getClip(); 226 Rectangle2D dataArea = chartPanel.getScreenDataArea(); 227 g2.clip(dataArea); 228 JFreeChart chart = chartPanel.getChart(); 229 XYPlot plot = (XYPlot) chart.getPlot(); 230 ValueAxis xAxis = plot.getDomainAxis(); 231 RectangleEdge xAxisEdge = plot.getDomainAxisEdge(); 232 for (Crosshair ch : this.xCrosshairs) { 233 if (ch.isVisible()) { 234 double x = ch.getValue(); 235 double xx = xAxis.valueToJava2D(x, dataArea, xAxisEdge); 236 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 237 drawVerticalCrosshair(g2, dataArea, xx, ch); 238 } else { 239 drawHorizontalCrosshair(g2, dataArea, xx, ch); 240 } 241 } 242 } 243 ValueAxis yAxis = plot.getRangeAxis(); 244 RectangleEdge yAxisEdge = plot.getRangeAxisEdge(); 245 for (Crosshair ch : this.yCrosshairs) { 246 if (ch.isVisible()) { 247 double y = ch.getValue(); 248 double yy = yAxis.valueToJava2D(y, dataArea, yAxisEdge); 249 if (plot.getOrientation() == PlotOrientation.VERTICAL) { 250 drawHorizontalCrosshair(g2, dataArea, yy, ch); 251 } else { 252 drawVerticalCrosshair(g2, dataArea, yy, ch); 253 } 254 } 255 } 256 g2.setClip(savedClip); 257 } 258 259 /** 260 * Draws a crosshair horizontally across the plot. 261 * 262 * @param g2 the graphics target. 263 * @param dataArea the data area. 264 * @param y the y-value in Java2D space. 265 * @param crosshair the crosshair. 266 */ 267 protected void drawHorizontalCrosshair(Graphics2D g2, Rectangle2D dataArea, 268 double y, Crosshair crosshair) { 269 270 if (y >= dataArea.getMinY() && y <= dataArea.getMaxY()) { 271 Line2D line = new Line2D.Double(dataArea.getMinX(), y, 272 dataArea.getMaxX(), y); 273 Paint savedPaint = g2.getPaint(); 274 Stroke savedStroke = g2.getStroke(); 275 g2.setPaint(crosshair.getPaint()); 276 g2.setStroke(crosshair.getStroke()); 277 g2.draw(line); 278 if (crosshair.isLabelVisible()) { 279 String label = crosshair.getLabelGenerator().generateLabel( 280 crosshair); 281 if (label != null && !label.isEmpty()) { 282 Font savedFont = g2.getFont(); 283 g2.setFont(crosshair.getLabelFont()); 284 RectangleAnchor anchor = crosshair.getLabelAnchor(); 285 RectangleInsets padding = crosshair.getLabelPadding(); 286 Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); 287 float xx = (float) pt.getX(); 288 float yy = (float) pt.getY(); 289 TextAnchor alignPt = textAlignPtForLabelAnchorH(anchor); 290 Shape hotspot = TextUtils.calculateRotatedStringBounds( 291 label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); 292 hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); 293 if (!dataArea.contains(hotspot.getBounds2D())) { 294 anchor = flipAnchorV(anchor); 295 pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); 296 xx = (float) pt.getX(); 297 yy = (float) pt.getY(); 298 if (anchor == RectangleAnchor.CENTER || alignPt.isHalfAscent()) { 299 double labelHeight = hotspot.getBounds2D().getHeight(); 300 double minY = dataArea.getY() + (labelHeight + padding.getTop() - padding.getBottom()) / 2.0; 301 double maxY = dataArea.getY() + dataArea.getHeight() - (labelHeight + padding.getBottom() - padding.getTop()) / 2.0; 302 if (yy < minY) { 303 yy = (float) (minY); 304 } else if (yy > maxY) { 305 yy = (float) (maxY); 306 } 307 } 308 alignPt = textAlignPtForLabelAnchorH(anchor); 309 hotspot = TextUtils.calculateRotatedStringBounds( 310 label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); 311 hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); 312 } 313 314 g2.setPaint(crosshair.getLabelBackgroundPaint()); 315 g2.fill(hotspot); 316 if (crosshair.isLabelOutlineVisible()) { 317 g2.setPaint(crosshair.getLabelOutlinePaint()); 318 g2.setStroke(crosshair.getLabelOutlineStroke()); 319 g2.draw(hotspot); 320 } 321 g2.setPaint(crosshair.getLabelPaint()); 322 TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); 323 g2.setFont(savedFont); 324 } 325 } 326 g2.setPaint(savedPaint); 327 g2.setStroke(savedStroke); 328 } 329 } 330 331 /** 332 * Draws a crosshair vertically on the plot. 333 * 334 * @param g2 the graphics target. 335 * @param dataArea the data area. 336 * @param x the x-value in Java2D space. 337 * @param crosshair the crosshair. 338 */ 339 protected void drawVerticalCrosshair(Graphics2D g2, Rectangle2D dataArea, 340 double x, Crosshair crosshair) { 341 342 if (x >= dataArea.getMinX() && x <= dataArea.getMaxX()) { 343 Line2D line = new Line2D.Double(x, dataArea.getMinY(), x, 344 dataArea.getMaxY()); 345 Paint savedPaint = g2.getPaint(); 346 Stroke savedStroke = g2.getStroke(); 347 g2.setPaint(crosshair.getPaint()); 348 g2.setStroke(crosshair.getStroke()); 349 g2.draw(line); 350 if (crosshair.isLabelVisible()) { 351 String label = crosshair.getLabelGenerator().generateLabel( 352 crosshair); 353 if (label != null && !label.isEmpty()) { 354 Font savedFont = g2.getFont(); 355 g2.setFont(crosshair.getLabelFont()); 356 RectangleAnchor anchor = crosshair.getLabelAnchor(); 357 RectangleInsets padding = crosshair.getLabelPadding(); 358 Point2D pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); 359 float xx = (float) pt.getX(); 360 float yy = (float) pt.getY(); 361 TextAnchor alignPt = textAlignPtForLabelAnchorV(anchor); 362 Shape hotspot = TextUtils.calculateRotatedStringBounds( 363 label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); 364 hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); 365 if (!dataArea.contains(hotspot.getBounds2D())) { 366 anchor = flipAnchorH(anchor); 367 pt = calculateLabelPoint(line, anchor, crosshair.getLabelXOffset(), crosshair.getLabelYOffset(), padding); 368 xx = (float) pt.getX(); 369 yy = (float) pt.getY(); 370 if (alignPt.isHorizontalCenter()) { 371 double labelWidth = hotspot.getBounds2D().getWidth(); 372 double minX = dataArea.getX() + (labelWidth + padding.getLeft() - padding.getRight()) / 2.0; 373 double maxX = dataArea.getX() + dataArea.getWidth() - (labelWidth + padding.getRight() - padding.getLeft()) / 2.0; 374 if (xx < minX) { 375 xx = (float) (minX); 376 } else if (xx > maxX) { 377 xx = (float) (maxX); 378 } 379 } 380 alignPt = textAlignPtForLabelAnchorV(anchor); 381 hotspot = TextUtils.calculateRotatedStringBounds( 382 label, g2, xx, yy, alignPt, 0.0, TextAnchor.CENTER); 383 hotspot = padding.createOutsetRectangle(hotspot.getBounds2D()); 384 } 385 g2.setPaint(crosshair.getLabelBackgroundPaint()); 386 g2.fill(hotspot); 387 if (crosshair.isLabelOutlineVisible()) { 388 g2.setPaint(crosshair.getLabelOutlinePaint()); 389 g2.setStroke(crosshair.getLabelOutlineStroke()); 390 g2.draw(hotspot); 391 } 392 g2.setPaint(crosshair.getLabelPaint()); 393 TextUtils.drawAlignedString(label, g2, xx, yy, alignPt); 394 g2.setFont(savedFont); 395 } 396 } 397 g2.setPaint(savedPaint); 398 g2.setStroke(savedStroke); 399 } 400 } 401 402 /** 403 * Calculates the anchor point for a label. 404 * 405 * @param line the line for the crosshair. 406 * @param anchor the anchor point. 407 * @param deltaX the x-offset. 408 * @param deltaY the y-offset. 409 * @param padding the label padding 410 * 411 * @return The anchor point. 412 */ 413 private Point2D calculateLabelPoint(Line2D line, RectangleAnchor anchor, 414 double deltaX, double deltaY, RectangleInsets padding) { 415 double x, y; 416 boolean left = (anchor == RectangleAnchor.BOTTOM_LEFT 417 || anchor == RectangleAnchor.LEFT 418 || anchor == RectangleAnchor.TOP_LEFT); 419 boolean right = (anchor == RectangleAnchor.BOTTOM_RIGHT 420 || anchor == RectangleAnchor.RIGHT 421 || anchor == RectangleAnchor.TOP_RIGHT); 422 boolean top = (anchor == RectangleAnchor.TOP_LEFT 423 || anchor == RectangleAnchor.TOP 424 || anchor == RectangleAnchor.TOP_RIGHT); 425 boolean bottom = (anchor == RectangleAnchor.BOTTOM_LEFT 426 || anchor == RectangleAnchor.BOTTOM 427 || anchor == RectangleAnchor.BOTTOM_RIGHT); 428 Rectangle rect = line.getBounds(); 429 430 // we expect the line to be vertical or horizontal 431 if (line.getX1() == line.getX2()) { // vertical 432 x = line.getX1(); 433 y = (line.getY1() + line.getY2()) / 2.0; 434 if (left) { 435 x = x - deltaX - padding.getRight(); 436 } else if (right) { 437 x = x + deltaX + padding.getLeft(); 438 } else { 439 x = x + (padding.getLeft() - padding.getRight()) / 2.0; 440 } 441 if (top) { 442 y = Math.min(line.getY1(), line.getY2()) + deltaY + padding.getTop(); 443 } else if (bottom) { 444 y = Math.max(line.getY1(), line.getY2()) - deltaY - padding.getBottom(); 445 } else { 446 y = y + (padding.getTop() - padding.getBottom()) / 2.0; 447 } 448 } 449 else { // horizontal 450 x = (line.getX1() + line.getX2()) / 2.0; 451 y = line.getY1(); 452 if (left) { 453 x = Math.min(line.getX1(), line.getX2()) + deltaX + padding.getLeft(); 454 } else if (right) { 455 x = Math.max(line.getX1(), line.getX2()) - deltaX - padding.getRight(); 456 } else { 457 x = x + (padding.getLeft() - padding.getRight()) / 2.0; 458 } 459 if (top) { 460 y = y - deltaY - padding.getBottom(); 461 } else if (bottom) { 462 y = y + deltaY + padding.getTop(); 463 } else { 464 y = y + (padding.getTop() - padding.getBottom()) / 2.0; 465 } 466 } 467 return new Point2D.Double(x, y); 468 } 469 470 /** 471 * Returns the text anchor that is used to align a label to its anchor 472 * point. 473 * 474 * @param anchor the anchor. 475 * 476 * @return The text alignment point. 477 */ 478 private TextAnchor textAlignPtForLabelAnchorV(RectangleAnchor anchor) { 479 TextAnchor result = TextAnchor.CENTER; 480 if (anchor.equals(RectangleAnchor.TOP_LEFT)) { 481 result = TextAnchor.TOP_RIGHT; 482 } 483 else if (anchor.equals(RectangleAnchor.TOP)) { 484 result = TextAnchor.TOP_CENTER; 485 } 486 else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { 487 result = TextAnchor.TOP_LEFT; 488 } 489 else if (anchor.equals(RectangleAnchor.LEFT)) { 490 result = TextAnchor.HALF_ASCENT_RIGHT; 491 } 492 else if (anchor.equals(RectangleAnchor.RIGHT)) { 493 result = TextAnchor.HALF_ASCENT_LEFT; 494 } 495 else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 496 result = TextAnchor.BOTTOM_RIGHT; 497 } 498 else if (anchor.equals(RectangleAnchor.BOTTOM)) { 499 result = TextAnchor.BOTTOM_CENTER; 500 } 501 else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 502 result = TextAnchor.BOTTOM_LEFT; 503 } 504 return result; 505 } 506 507 /** 508 * Returns the text anchor that is used to align a label to its anchor 509 * point. 510 * 511 * @param anchor the anchor. 512 * 513 * @return The text alignment point. 514 */ 515 private TextAnchor textAlignPtForLabelAnchorH(RectangleAnchor anchor) { 516 TextAnchor result = TextAnchor.CENTER; 517 if (anchor.equals(RectangleAnchor.TOP_LEFT)) { 518 result = TextAnchor.BOTTOM_LEFT; 519 } 520 else if (anchor.equals(RectangleAnchor.TOP)) { 521 result = TextAnchor.BOTTOM_CENTER; 522 } 523 else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { 524 result = TextAnchor.BOTTOM_RIGHT; 525 } 526 else if (anchor.equals(RectangleAnchor.LEFT)) { 527 result = TextAnchor.HALF_ASCENT_LEFT; 528 } 529 else if (anchor.equals(RectangleAnchor.RIGHT)) { 530 result = TextAnchor.HALF_ASCENT_RIGHT; 531 } 532 else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 533 result = TextAnchor.TOP_LEFT; 534 } 535 else if (anchor.equals(RectangleAnchor.BOTTOM)) { 536 result = TextAnchor.TOP_CENTER; 537 } 538 else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 539 result = TextAnchor.TOP_RIGHT; 540 } 541 return result; 542 } 543 544 private RectangleAnchor flipAnchorH(RectangleAnchor anchor) { 545 RectangleAnchor result = anchor; 546 if (anchor.equals(RectangleAnchor.TOP_LEFT)) { 547 result = RectangleAnchor.TOP_RIGHT; 548 } 549 else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { 550 result = RectangleAnchor.TOP_LEFT; 551 } 552 else if (anchor.equals(RectangleAnchor.LEFT)) { 553 result = RectangleAnchor.RIGHT; 554 } 555 else if (anchor.equals(RectangleAnchor.RIGHT)) { 556 result = RectangleAnchor.LEFT; 557 } 558 else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 559 result = RectangleAnchor.BOTTOM_RIGHT; 560 } 561 else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 562 result = RectangleAnchor.BOTTOM_LEFT; 563 } 564 return result; 565 } 566 567 private RectangleAnchor flipAnchorV(RectangleAnchor anchor) { 568 RectangleAnchor result = anchor; 569 if (anchor.equals(RectangleAnchor.TOP_LEFT)) { 570 result = RectangleAnchor.BOTTOM_LEFT; 571 } 572 else if (anchor.equals(RectangleAnchor.TOP_RIGHT)) { 573 result = RectangleAnchor.BOTTOM_RIGHT; 574 } 575 else if (anchor.equals(RectangleAnchor.TOP)) { 576 result = RectangleAnchor.BOTTOM; 577 } 578 else if (anchor.equals(RectangleAnchor.BOTTOM)) { 579 result = RectangleAnchor.TOP; 580 } 581 else if (anchor.equals(RectangleAnchor.BOTTOM_LEFT)) { 582 result = RectangleAnchor.TOP_LEFT; 583 } 584 else if (anchor.equals(RectangleAnchor.BOTTOM_RIGHT)) { 585 result = RectangleAnchor.TOP_RIGHT; 586 } 587 return result; 588 } 589 590 /** 591 * Tests this overlay for equality with an arbitrary object. 592 * 593 * @param obj the object ({@code null} permitted). 594 * 595 * @return A boolean. 596 */ 597 @Override 598 public boolean equals(Object obj) { 599 if (obj == this) { 600 return true; 601 } 602 if (!(obj instanceof CrosshairOverlay)) { 603 return false; 604 } 605 CrosshairOverlay that = (CrosshairOverlay) obj; 606 if (!this.xCrosshairs.equals(that.xCrosshairs)) { 607 return false; 608 } 609 if (!this.yCrosshairs.equals(that.yCrosshairs)) { 610 return false; 611 } 612 return true; 613 } 614 615 /** 616 * Returns a clone of this instance. 617 * 618 * @return A clone of this instance. 619 * 620 * @throws java.lang.CloneNotSupportedException if there is some problem 621 * with the cloning. 622 */ 623 @Override 624 public Object clone() throws CloneNotSupportedException { 625 CrosshairOverlay clone = (CrosshairOverlay) super.clone(); 626 clone.xCrosshairs = (List<Crosshair>) ObjectUtils.deepClone(this.xCrosshairs); 627 clone.yCrosshairs = (List<Crosshair>) ObjectUtils.deepClone(this.yCrosshairs); 628 return clone; 629 } 630 631}