Source for org.jfree.chart.annotations.XYPointerAnnotation

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ------------------------
  28:  * XYPointerAnnotation.java
  29:  * ------------------------
  30:  * (C) Copyright 2003-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * $Id: XYPointerAnnotation.java,v 1.4.2.5 2007/03/06 16:12:18 mungady Exp $
  36:  *
  37:  * Changes:
  38:  * --------
  39:  * 21-May-2003 : Version 1 (DG);
  40:  * 10-Jun-2003 : Changed BoundsAnchor to TextAnchor (DG);
  41:  * 02-Jul-2003 : Added accessor methods and simplified constructor (DG);
  42:  * 19-Aug-2003 : Implemented Cloneable (DG);
  43:  * 13-Oct-2003 : Fixed bug where arrow paint is not set correctly (DG);
  44:  * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
  45:  * 29-Sep-2004 : Changes to draw() method signature (DG);
  46:  * ------------- JFREECHART 1.0.x ---------------------------------------------
  47:  * 20-Feb-2006 : Correction for equals() method (fixes bug 1435160) (DG);
  48:  * 12-Jul-2006 : Fix drawing for PlotOrientation.HORIZONTAL, thanks to 
  49:  *               Skunk (DG);
  50:  *
  51:  */
  52: 
  53: package org.jfree.chart.annotations;
  54: 
  55: import java.awt.BasicStroke;
  56: import java.awt.Color;
  57: import java.awt.Graphics2D;
  58: import java.awt.Paint;
  59: import java.awt.Stroke;
  60: import java.awt.geom.GeneralPath;
  61: import java.awt.geom.Line2D;
  62: import java.awt.geom.Rectangle2D;
  63: import java.io.IOException;
  64: import java.io.ObjectInputStream;
  65: import java.io.ObjectOutputStream;
  66: import java.io.Serializable;
  67: 
  68: import org.jfree.chart.HashUtilities;
  69: import org.jfree.chart.axis.ValueAxis;
  70: import org.jfree.chart.plot.Plot;
  71: import org.jfree.chart.plot.PlotOrientation;
  72: import org.jfree.chart.plot.PlotRenderingInfo;
  73: import org.jfree.chart.plot.XYPlot;
  74: import org.jfree.io.SerialUtilities;
  75: import org.jfree.text.TextUtilities;
  76: import org.jfree.ui.RectangleEdge;
  77: import org.jfree.util.ObjectUtilities;
  78: import org.jfree.util.PublicCloneable;
  79: 
  80: /**
  81:  * An arrow and label that can be placed on an 
  82:  * {@link org.jfree.chart.plot.XYPlot}.  The arrow is drawn at a user-definable 
  83:  * angle so that it points towards the (x, y) location for the annotation.  
  84:  * <p>
  85:  * The arrow length (and its offset from the (x, y) location) is controlled by 
  86:  * the tip radius and the base radius attributes.  Imagine two circles around 
  87:  * the (x, y) coordinate: the inner circle defined by the tip radius, and the 
  88:  * outer circle defined by the base radius.  Now, draw the arrow starting at 
  89:  * some point on the outer circle (the point is determined by the angle), with 
  90:  * the arrow tip being drawn at a corresponding point on the inner circle.
  91:  *
  92:  */
  93: public class XYPointerAnnotation extends XYTextAnnotation 
  94:                                  implements Cloneable, PublicCloneable, 
  95:                                             Serializable {
  96: 
  97:     /** For serialization. */
  98:     private static final long serialVersionUID = -4031161445009858551L;
  99:     
 100:     /** The default tip radius (in Java2D units). */
 101:     public static final double DEFAULT_TIP_RADIUS = 10.0;
 102:     
 103:     /** The default base radius (in Java2D units). */
 104:     public static final double DEFAULT_BASE_RADIUS = 30.0;
 105:     
 106:     /** The default label offset (in Java2D units). */
 107:     public static final double DEFAULT_LABEL_OFFSET = 3.0;
 108:     
 109:     /** The default arrow length (in Java2D units). */
 110:     public static final double DEFAULT_ARROW_LENGTH = 5.0;
 111: 
 112:     /** The default arrow width (in Java2D units). */
 113:     public static final double DEFAULT_ARROW_WIDTH = 3.0;
 114:     
 115:     /** The angle of the arrow's line (in radians). */
 116:     private double angle;
 117: 
 118:     /** 
 119:      * The radius from the (x, y) point to the tip of the arrow (in Java2D 
 120:      * units). 
 121:      */
 122:     private double tipRadius;
 123: 
 124:     /** 
 125:      * The radius from the (x, y) point to the start of the arrow line (in 
 126:      * Java2D units). 
 127:      */
 128:     private double baseRadius;
 129: 
 130:     /** The length of the arrow head (in Java2D units). */
 131:     private double arrowLength;
 132: 
 133:     /** The arrow width (in Java2D units, per side). */
 134:     private double arrowWidth;
 135:     
 136:     /** The arrow stroke. */
 137:     private transient Stroke arrowStroke;
 138: 
 139:     /** The arrow paint. */
 140:     private transient Paint arrowPaint;
 141:     
 142:     /** The radius from the base point to the anchor point for the label. */
 143:     private double labelOffset;
 144: 
 145:     /**
 146:      * Creates a new label and arrow annotation.
 147:      *
 148:      * @param label  the label (<code>null</code> permitted).
 149:      * @param x  the x-coordinate (measured against the chart's domain axis).
 150:      * @param y  the y-coordinate (measured against the chart's range axis).
 151:      * @param angle  the angle of the arrow's line (in radians).
 152:      */
 153:     public XYPointerAnnotation(String label, double x, double y, double angle) {
 154: 
 155:         super(label, x, y);
 156:         this.angle = angle;
 157:         this.tipRadius = DEFAULT_TIP_RADIUS;
 158:         this.baseRadius = DEFAULT_BASE_RADIUS;
 159:         this.arrowLength = DEFAULT_ARROW_LENGTH;
 160:         this.arrowWidth = DEFAULT_ARROW_WIDTH;
 161:         this.labelOffset = DEFAULT_LABEL_OFFSET;
 162:         this.arrowStroke = new BasicStroke(1.0f);
 163:         this.arrowPaint = Color.black;
 164: 
 165:     }
 166:     
 167:     /**
 168:      * Returns the angle of the arrow.
 169:      * 
 170:      * @return The angle (in radians).
 171:      * 
 172:      * @see #setAngle(double)
 173:      */
 174:     public double getAngle() {
 175:         return this.angle;
 176:     }
 177:     
 178:     /**
 179:      * Sets the angle of the arrow.
 180:      * 
 181:      * @param angle  the angle (in radians).
 182:      * 
 183:      * @see #getAngle()
 184:      */
 185:     public void setAngle(double angle) {
 186:         this.angle = angle;
 187:     }
 188:     
 189:     /**
 190:      * Returns the tip radius.
 191:      * 
 192:      * @return The tip radius (in Java2D units).
 193:      * 
 194:      * @see #setTipRadius(double)
 195:      */
 196:     public double getTipRadius() {
 197:         return this.tipRadius;
 198:     }
 199:     
 200:     /**
 201:      * Sets the tip radius.
 202:      * 
 203:      * @param radius  the radius (in Java2D units).
 204:      * 
 205:      * @see #getTipRadius()
 206:      */
 207:     public void setTipRadius(double radius) {
 208:         this.tipRadius = radius;
 209:     }
 210:     
 211:     /**
 212:      * Returns the base radius.
 213:      * 
 214:      * @return The base radius (in Java2D units).
 215:      * 
 216:      * @see #setBaseRadius(double)
 217:      */
 218:     public double getBaseRadius() {
 219:         return this.baseRadius;
 220:     }
 221:     
 222:     /**
 223:      * Sets the base radius.
 224:      * 
 225:      * @param radius  the radius (in Java2D units).
 226:      * 
 227:      * @see #getBaseRadius()
 228:      */
 229:     public void setBaseRadius(double radius) {
 230:         this.baseRadius = radius;
 231:     }
 232: 
 233:     /**
 234:      * Returns the label offset.
 235:      * 
 236:      * @return The label offset (in Java2D units).
 237:      * 
 238:      * @see #setLabelOffset(double)
 239:      */
 240:     public double getLabelOffset() {
 241:         return this.labelOffset;
 242:     }
 243:     
 244:     /**
 245:      * Sets the label offset (from the arrow base, continuing in a straight 
 246:      * line, in Java2D units).
 247:      * 
 248:      * @param offset  the offset (in Java2D units).
 249:      * 
 250:      * @see #getLabelOffset()
 251:      */
 252:     public void setLabelOffset(double offset) {
 253:         this.labelOffset = offset;
 254:     }
 255:     
 256:     /**
 257:      * Returns the arrow length.
 258:      * 
 259:      * @return The arrow length.
 260:      * 
 261:      * @see #setArrowLength(double)
 262:      */
 263:     public double getArrowLength() {
 264:         return this.arrowLength;
 265:     }
 266:     
 267:     /**
 268:      * Sets the arrow length.
 269:      * 
 270:      * @param length  the length.
 271:      * 
 272:      * @see #getArrowLength()
 273:      */
 274:     public void setArrowLength(double length) {
 275:         this.arrowLength = length;
 276:     }
 277: 
 278:     /**
 279:      * Returns the arrow width.
 280:      * 
 281:      * @return The arrow width (in Java2D units).
 282:      * 
 283:      * @see #setArrowWidth(double)
 284:      */
 285:     public double getArrowWidth() {
 286:         return this.arrowWidth;
 287:     }
 288:     
 289:     /**
 290:      * Sets the arrow width.
 291:      * 
 292:      * @param width  the width (in Java2D units).
 293:      * 
 294:      * @see #getArrowWidth()
 295:      */
 296:     public void setArrowWidth(double width) {
 297:         this.arrowWidth = width;
 298:     }
 299:     
 300:     /** 
 301:      * Returns the stroke used to draw the arrow line.
 302:      * 
 303:      * @return The arrow stroke (never <code>null</code>).
 304:      * 
 305:      * @see #setArrowStroke(Stroke)
 306:      */
 307:     public Stroke getArrowStroke() {
 308:         return this.arrowStroke;
 309:     }
 310: 
 311:     /** 
 312:      * Sets the stroke used to draw the arrow line.
 313:      * 
 314:      * @param stroke  the stroke (<code>null</code> not permitted).
 315:      * 
 316:      * @see #getArrowStroke()
 317:      */
 318:     public void setArrowStroke(Stroke stroke) {
 319:         if (stroke == null) {
 320:             throw new IllegalArgumentException("Null 'stroke' not permitted.");
 321:         }
 322:         this.arrowStroke = stroke;
 323:     }
 324:     
 325:     /**
 326:      * Returns the paint used for the arrow.
 327:      * 
 328:      * @return The arrow paint (never <code>null</code>).
 329:      * 
 330:      * @see #setArrowPaint(Paint)
 331:      */
 332:     public Paint getArrowPaint() {
 333:         return this.arrowPaint;
 334:     }
 335:     
 336:     /**
 337:      * Sets the paint used for the arrow.
 338:      * 
 339:      * @param paint  the arrow paint (<code>null</code> not permitted).
 340:      * 
 341:      * @see #getArrowPaint()
 342:      */
 343:     public void setArrowPaint(Paint paint) {
 344:         if (paint == null) {
 345:             throw new IllegalArgumentException("Null 'paint' argument.");
 346:         }
 347:         this.arrowPaint = paint;
 348:     }
 349:     
 350:     /**
 351:      * Draws the annotation.
 352:      *
 353:      * @param g2  the graphics device.
 354:      * @param plot  the plot.
 355:      * @param dataArea  the data area.
 356:      * @param domainAxis  the domain axis.
 357:      * @param rangeAxis  the range axis.
 358:      * @param rendererIndex  the renderer index.
 359:      * @param info  the plot rendering info.
 360:      */
 361:     public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
 362:                      ValueAxis domainAxis, ValueAxis rangeAxis, 
 363:                      int rendererIndex,
 364:                      PlotRenderingInfo info) {
 365: 
 366:         PlotOrientation orientation = plot.getOrientation();
 367:         RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
 368:                 plot.getDomainAxisLocation(), orientation);
 369:         RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
 370:                 plot.getRangeAxisLocation(), orientation);
 371:         double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge);
 372:         double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge);
 373:         if (orientation == PlotOrientation.HORIZONTAL) {
 374:             double temp = j2DX;
 375:             j2DX = j2DY;
 376:             j2DY = temp;
 377:         }
 378:         double startX = j2DX + Math.cos(this.angle) * this.baseRadius;
 379:         double startY = j2DY + Math.sin(this.angle) * this.baseRadius;
 380: 
 381:         double endX = j2DX + Math.cos(this.angle) * this.tipRadius;
 382:         double endY = j2DY + Math.sin(this.angle) * this.tipRadius;
 383: 
 384:         double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength;
 385:         double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength;
 386: 
 387:         double arrowLeftX = arrowBaseX 
 388:             + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
 389:         double arrowLeftY = arrowBaseY 
 390:             + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;
 391: 
 392:         double arrowRightX = arrowBaseX 
 393:             - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
 394:         double arrowRightY = arrowBaseY 
 395:             - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;
 396: 
 397:         GeneralPath arrow = new GeneralPath();
 398:         arrow.moveTo((float) endX, (float) endY);
 399:         arrow.lineTo((float) arrowLeftX, (float) arrowLeftY);
 400:         arrow.lineTo((float) arrowRightX, (float) arrowRightY);
 401:         arrow.closePath();
 402: 
 403:         g2.setStroke(this.arrowStroke);
 404:         g2.setPaint(this.arrowPaint);
 405:         Line2D line = new Line2D.Double(startX, startY, endX, endY);
 406:         g2.draw(line);
 407:         g2.fill(arrow);
 408: 
 409:         // draw the label
 410:         g2.setFont(getFont());
 411:         g2.setPaint(getPaint());
 412:         double labelX = j2DX 
 413:             + Math.cos(this.angle) * (this.baseRadius + this.labelOffset);
 414:         double labelY = j2DY 
 415:             + Math.sin(this.angle) * (this.baseRadius + this.labelOffset);
 416:         Rectangle2D hotspot = TextUtilities.drawAlignedString(getText(), 
 417:                 g2, (float) labelX, (float) labelY, getTextAnchor());
 418: 
 419:         String toolTip = getToolTipText();
 420:         String url = getURL();
 421:         if (toolTip != null || url != null) {
 422:             addEntity(info, hotspot, rendererIndex, toolTip, url);
 423:         }
 424:         
 425:     }
 426:     
 427:     /**
 428:      * Tests this annotation for equality with an arbitrary object.
 429:      * 
 430:      * @param obj  the object (<code>null</code> permitted).
 431:      * 
 432:      * @return <code>true</code> or <code>false</code>.
 433:      */
 434:     public boolean equals(Object obj) {
 435:         if (obj == this) {
 436:             return true;
 437:         }
 438:         if (!(obj instanceof XYPointerAnnotation)) {
 439:             return false;
 440:         }
 441:         if (!super.equals(obj)) {
 442:             return false;
 443:         }
 444:         XYPointerAnnotation that = (XYPointerAnnotation) obj;
 445:         if (this.angle != that.angle) {
 446:             return false;
 447:         }
 448:         if (this.tipRadius != that.tipRadius) {
 449:             return false;
 450:         }
 451:         if (this.baseRadius != that.baseRadius) {
 452:             return false;
 453:         }
 454:         if (this.arrowLength != that.arrowLength) {
 455:             return false;
 456:         }
 457:         if (this.arrowWidth != that.arrowWidth) {
 458:             return false;
 459:         }
 460:         if (!this.arrowPaint.equals(that.arrowPaint)) {
 461:             return false;
 462:         }
 463:         if (!ObjectUtilities.equal(this.arrowStroke, that.arrowStroke)) {
 464:             return false;
 465:         }
 466:         if (this.labelOffset != that.labelOffset) {
 467:             return false;
 468:         }
 469:         return true;
 470:     }
 471:     
 472:     /**
 473:      * Returns a hash code for this instance.
 474:      * 
 475:      * @return A hash code.
 476:      */
 477:     public int hashCode() {
 478:         int result = super.hashCode();
 479:         long temp = Double.doubleToLongBits(this.angle);
 480:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 481:         temp = Double.doubleToLongBits(this.tipRadius);
 482:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 483:         temp = Double.doubleToLongBits(this.baseRadius);
 484:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 485:         temp = Double.doubleToLongBits(this.arrowLength);
 486:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 487:         temp = Double.doubleToLongBits(this.arrowWidth);
 488:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 489:         result = result * 37 + HashUtilities.hashCodeForPaint(this.arrowPaint);
 490:         result = result * 37 + this.arrowStroke.hashCode();
 491:         temp = Double.doubleToLongBits(this.labelOffset);
 492:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 493:         return super.hashCode();
 494:     }
 495:     
 496:     /**
 497:      * Returns a clone of the annotation.
 498:      * 
 499:      * @return A clone.
 500:      * 
 501:      * @throws CloneNotSupportedException  if the annotation can't be cloned.
 502:      */
 503:     public Object clone() throws CloneNotSupportedException {
 504:         return super.clone();
 505:     }
 506: 
 507:     /**
 508:      * Provides serialization support.
 509:      *
 510:      * @param stream  the output stream.
 511:      *
 512:      * @throws IOException if there is an I/O error.
 513:      */
 514:     private void writeObject(ObjectOutputStream stream) throws IOException {
 515:         stream.defaultWriteObject();
 516:         SerialUtilities.writePaint(this.arrowPaint, stream);
 517:         SerialUtilities.writeStroke(this.arrowStroke, stream);
 518:     }
 519: 
 520:     /**
 521:      * Provides serialization support.
 522:      *
 523:      * @param stream  the input stream.
 524:      *
 525:      * @throws IOException  if there is an I/O error.
 526:      * @throws ClassNotFoundException  if there is a classpath problem.
 527:      */
 528:     private void readObject(ObjectInputStream stream) 
 529:         throws IOException, ClassNotFoundException {
 530:         stream.defaultReadObject();
 531:         this.arrowPaint = SerialUtilities.readPaint(stream);
 532:         this.arrowStroke = SerialUtilities.readStroke(stream);
 533:     }
 534: 
 535: }