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-2006, 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-2006, 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.3 2006/07/12 15:42:54 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.0 ---------------------------------------------
  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.axis.ValueAxis;
  69: import org.jfree.chart.plot.Plot;
  70: import org.jfree.chart.plot.PlotOrientation;
  71: import org.jfree.chart.plot.PlotRenderingInfo;
  72: import org.jfree.chart.plot.XYPlot;
  73: import org.jfree.io.SerialUtilities;
  74: import org.jfree.text.TextUtilities;
  75: import org.jfree.ui.RectangleEdge;
  76: import org.jfree.util.ObjectUtilities;
  77: import org.jfree.util.PublicCloneable;
  78: 
  79: /**
  80:  * An arrow and label that can be placed on an 
  81:  * {@link org.jfree.chart.plot.XYPlot}.  The arrow is drawn at a user-definable 
  82:  * angle so that it points towards the (x, y) location for the annotation.  
  83:  * <p>
  84:  * The arrow length (and its offset from the (x, y) location) is controlled by 
  85:  * the tip radius and the base radius attributes.  Imagine two circles around 
  86:  * the (x, y) coordinate: the inner circle defined by the tip radius, and the 
  87:  * outer circle defined by the base radius.  Now, draw the arrow starting at 
  88:  * some point on the outer circle (the point is determined by the angle), with 
  89:  * the arrow tip being drawn at a corresponding point on the inner circle.
  90:  *
  91:  */
  92: public class XYPointerAnnotation extends XYTextAnnotation 
  93:                                  implements Cloneable, PublicCloneable, 
  94:                                             Serializable {
  95: 
  96:     /** For serialization. */
  97:     private static final long serialVersionUID = -4031161445009858551L;
  98:     
  99:     /** The default tip radius (in Java2D units). */
 100:     public static final double DEFAULT_TIP_RADIUS = 10.0;
 101:     
 102:     /** The default base radius (in Java2D units). */
 103:     public static final double DEFAULT_BASE_RADIUS = 30.0;
 104:     
 105:     /** The default label offset (in Java2D units). */
 106:     public static final double DEFAULT_LABEL_OFFSET = 3.0;
 107:     
 108:     /** The default arrow length (in Java2D units). */
 109:     public static final double DEFAULT_ARROW_LENGTH = 5.0;
 110: 
 111:     /** The default arrow width (in Java2D units). */
 112:     public static final double DEFAULT_ARROW_WIDTH = 3.0;
 113:     
 114:     /** The angle of the arrow's line (in radians). */
 115:     private double angle;
 116: 
 117:     /** 
 118:      * The radius from the (x, y) point to the tip of the arrow (in Java2D 
 119:      * units). 
 120:      */
 121:     private double tipRadius;
 122: 
 123:     /** 
 124:      * The radius from the (x, y) point to the start of the arrow line (in 
 125:      * Java2D units). 
 126:      */
 127:     private double baseRadius;
 128: 
 129:     /** The length of the arrow head (in Java2D units). */
 130:     private double arrowLength;
 131: 
 132:     /** The arrow width (in Java2D units, per side). */
 133:     private double arrowWidth;
 134:     
 135:     /** The arrow stroke. */
 136:     private transient Stroke arrowStroke;
 137: 
 138:     /** The arrow paint. */
 139:     private transient Paint arrowPaint;
 140:     
 141:     /** The radius from the base point to the anchor point for the label. */
 142:     private double labelOffset;
 143: 
 144:     /**
 145:      * Creates a new label and arrow annotation.
 146:      *
 147:      * @param label  the label (<code>null</code> permitted).
 148:      * @param x  the x-coordinate (measured against the chart's domain axis).
 149:      * @param y  the y-coordinate (measured against the chart's range axis).
 150:      * @param angle  the angle of the arrow's line (in radians).
 151:      */
 152:     public XYPointerAnnotation(String label, double x, double y, double angle) {
 153: 
 154:         super(label, x, y);
 155:         this.angle = angle;
 156:         this.tipRadius = DEFAULT_TIP_RADIUS;
 157:         this.baseRadius = DEFAULT_BASE_RADIUS;
 158:         this.arrowLength = DEFAULT_ARROW_LENGTH;
 159:         this.arrowWidth = DEFAULT_ARROW_WIDTH;
 160:         this.labelOffset = DEFAULT_LABEL_OFFSET;
 161:         this.arrowStroke = new BasicStroke(1.0f);
 162:         this.arrowPaint = Color.black;
 163: 
 164:     }
 165:     
 166:     /**
 167:      * Returns the angle of the arrow.
 168:      * 
 169:      * @return The angle (in radians).
 170:      */
 171:     public double getAngle() {
 172:         return this.angle;
 173:     }
 174:     
 175:     /**
 176:      * Sets the angle of the arrow.
 177:      * 
 178:      * @param angle  the angle (in radians).
 179:      */
 180:     public void setAngle(double angle) {
 181:         this.angle = angle;
 182:     }
 183:     
 184:     /**
 185:      * Returns the tip radius.
 186:      * 
 187:      * @return The tip radius (in Java2D units).
 188:      */
 189:     public double getTipRadius() {
 190:         return this.tipRadius;
 191:     }
 192:     
 193:     /**
 194:      * Sets the tip radius.
 195:      * 
 196:      * @param radius  the radius (in Java2D units).
 197:      */
 198:     public void setTipRadius(double radius) {
 199:         this.tipRadius = radius;
 200:     }
 201:     
 202:     /**
 203:      * Sets the base radius.
 204:      * 
 205:      * @return The base radius (in Java2D units).
 206:      */
 207:     public double getBaseRadius() {
 208:         return this.baseRadius;
 209:     }
 210:     
 211:     /**
 212:      * Sets the base radius.
 213:      * 
 214:      * @param radius  the radius (in Java2D units).
 215:      */
 216:     public void setBaseRadius(double radius) {
 217:         this.baseRadius = radius;
 218:     }
 219: 
 220:     /**
 221:      * Sets the label offset.
 222:      * 
 223:      * @return The label offset (in Java2D units).
 224:      */
 225:     public double getLabelOffset() {
 226:         return this.labelOffset;
 227:     }
 228:     
 229:     /**
 230:      * Sets the label offset (from the arrow base, continuing in a straight 
 231:      * line, in Java2D units).
 232:      * 
 233:      * @param offset  the offset (in Java2D units).
 234:      */
 235:     public void setLabelOffset(double offset) {
 236:         this.labelOffset = offset;
 237:     }
 238:     
 239:     /**
 240:      * Returns the arrow length.
 241:      * 
 242:      * @return The arrow length.
 243:      */
 244:     public double getArrowLength() {
 245:         return this.arrowLength;
 246:     }
 247:     
 248:     /**
 249:      * Sets the arrow length.
 250:      * 
 251:      * @param length  the length.
 252:      */
 253:     public void setArrowLength(double length) {
 254:         this.arrowLength = length;
 255:     }
 256: 
 257:     /**
 258:      * Returns the arrow width.
 259:      * 
 260:      * @return The arrow width (in Java2D units).
 261:      */
 262:     public double getArrowWidth() {
 263:         return this.arrowWidth;
 264:     }
 265:     
 266:     /**
 267:      * Sets the arrow width.
 268:      * 
 269:      * @param width  the width (in Java2D units).
 270:      */
 271:     public void setArrowWidth(double width) {
 272:         this.arrowWidth = width;
 273:     }
 274:     
 275:     /** 
 276:      * Returns the stroke used to draw the arrow line.
 277:      * 
 278:      * @return The arrow stroke (never <code>null</code>).
 279:      */
 280:     public Stroke getArrowStroke() {
 281:         return this.arrowStroke;
 282:     }
 283: 
 284:     /** 
 285:      * Sets the stroke used to draw the arrow line.
 286:      * 
 287:      * @param stroke  the stroke (<code>null</code> not permitted).
 288:      */
 289:     public void setArrowStroke(Stroke stroke) {
 290:         if (stroke == null) {
 291:             throw new IllegalArgumentException("Null 'stroke' not permitted.");
 292:         }
 293:         this.arrowStroke = stroke;
 294:     }
 295:     
 296:     /**
 297:      * Sets the paint used for the arrow.
 298:      * 
 299:      * @return The arrow paint (never <code>null</code>).
 300:      */
 301:     public Paint getArrowPaint() {
 302:         return this.arrowPaint;
 303:     }
 304:     
 305:     /**
 306:      * Sets the paint used for the arrow.
 307:      * 
 308:      * @param paint  the arrow paint (<code>null</code> not permitted).
 309:      */
 310:     public void setArrowPaint(Paint paint) {
 311:         this.arrowPaint = paint;
 312:     }
 313:     
 314:     /**
 315:      * Draws the annotation.
 316:      *
 317:      * @param g2  the graphics device.
 318:      * @param plot  the plot.
 319:      * @param dataArea  the data area.
 320:      * @param domainAxis  the domain axis.
 321:      * @param rangeAxis  the range axis.
 322:      * @param rendererIndex  the renderer index.
 323:      * @param info  the plot rendering info.
 324:      */
 325:     public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
 326:                      ValueAxis domainAxis, ValueAxis rangeAxis, 
 327:                      int rendererIndex,
 328:                      PlotRenderingInfo info) {
 329: 
 330:         PlotOrientation orientation = plot.getOrientation();
 331:         RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
 332:                 plot.getDomainAxisLocation(), orientation);
 333:         RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
 334:                 plot.getRangeAxisLocation(), orientation);
 335:         double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge);
 336:         double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge);
 337:         if (orientation == PlotOrientation.HORIZONTAL) {
 338:             double temp = j2DX;
 339:             j2DX = j2DY;
 340:             j2DY = temp;
 341:         }
 342:         double startX = j2DX + Math.cos(this.angle) * this.baseRadius;
 343:         double startY = j2DY + Math.sin(this.angle) * this.baseRadius;
 344: 
 345:         double endX = j2DX + Math.cos(this.angle) * this.tipRadius;
 346:         double endY = j2DY + Math.sin(this.angle) * this.tipRadius;
 347: 
 348:         double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength;
 349:         double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength;
 350: 
 351:         double arrowLeftX = arrowBaseX 
 352:             + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
 353:         double arrowLeftY = arrowBaseY 
 354:             + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;
 355: 
 356:         double arrowRightX = arrowBaseX 
 357:             - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth;
 358:         double arrowRightY = arrowBaseY 
 359:             - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth;
 360: 
 361:         GeneralPath arrow = new GeneralPath();
 362:         arrow.moveTo((float) endX, (float) endY);
 363:         arrow.lineTo((float) arrowLeftX, (float) arrowLeftY);
 364:         arrow.lineTo((float) arrowRightX, (float) arrowRightY);
 365:         arrow.closePath();
 366: 
 367:         g2.setStroke(this.arrowStroke);
 368:         g2.setPaint(this.arrowPaint);
 369:         Line2D line = new Line2D.Double(startX, startY, endX, endY);
 370:         g2.draw(line);
 371:         g2.fill(arrow);
 372: 
 373:         // draw the label
 374:         g2.setFont(getFont());
 375:         g2.setPaint(getPaint());
 376:         double labelX = j2DX 
 377:             + Math.cos(this.angle) * (this.baseRadius + this.labelOffset);
 378:         double labelY = j2DY 
 379:             + Math.sin(this.angle) * (this.baseRadius + this.labelOffset);
 380:         Rectangle2D hotspot = TextUtilities.drawAlignedString(getText(), 
 381:                 g2, (float) labelX, (float) labelY, getTextAnchor());
 382: 
 383:         String toolTip = getToolTipText();
 384:         String url = getURL();
 385:         if (toolTip != null || url != null) {
 386:             addEntity(info, hotspot, rendererIndex, toolTip, url);
 387:         }
 388:         
 389:     }
 390:     
 391:     /**
 392:      * Tests this annotation for equality with an arbitrary object.
 393:      * 
 394:      * @param obj  the object (<code>null</code> permitted).
 395:      * 
 396:      * @return <code>true</code> or <code>false</code>.
 397:      */
 398:     public boolean equals(Object obj) {
 399:         
 400:         if (obj == null) {
 401:             return false;
 402:         }
 403:         if (obj == this) {
 404:             return true;
 405:         }
 406:         if (!(obj instanceof XYPointerAnnotation)) {
 407:             return false;
 408:         }
 409:         if (!super.equals(obj)) {
 410:             return false;
 411:         }
 412:         XYPointerAnnotation that = (XYPointerAnnotation) obj;
 413:         if (this.angle != that.angle) {
 414:             return false;
 415:         }
 416:         if (this.tipRadius != that.tipRadius) {
 417:             return false;
 418:         }
 419:         if (this.baseRadius != that.baseRadius) {
 420:             return false;
 421:         }
 422:         if (this.arrowLength != that.arrowLength) {
 423:             return false;
 424:         }
 425:         if (this.arrowWidth != that.arrowWidth) {
 426:             return false;
 427:         }
 428:         if (!this.arrowPaint.equals(that.arrowPaint)) {
 429:             return false;
 430:         }
 431:         if (!ObjectUtilities.equal(this.arrowStroke, that.arrowStroke)) {
 432:             return false;
 433:         }
 434:         if (this.labelOffset != that.labelOffset) {
 435:             return false;
 436:         }
 437:         return true;
 438:     }
 439:     
 440:     /**
 441:      * Returns a clone of the annotation.
 442:      * 
 443:      * @return A clone.
 444:      * 
 445:      * @throws CloneNotSupportedException  if the annotation can't be cloned.
 446:      */
 447:     public Object clone() throws CloneNotSupportedException {
 448:         return super.clone();
 449:     }
 450: 
 451:     /**
 452:      * Provides serialization support.
 453:      *
 454:      * @param stream  the output stream.
 455:      *
 456:      * @throws IOException if there is an I/O error.
 457:      */
 458:     private void writeObject(ObjectOutputStream stream) throws IOException {
 459:         stream.defaultWriteObject();
 460:         SerialUtilities.writePaint(this.arrowPaint, stream);
 461:         SerialUtilities.writeStroke(this.arrowStroke, stream);
 462:     }
 463: 
 464:     /**
 465:      * Provides serialization support.
 466:      *
 467:      * @param stream  the input stream.
 468:      *
 469:      * @throws IOException  if there is an I/O error.
 470:      * @throws ClassNotFoundException  if there is a classpath problem.
 471:      */
 472:     private void readObject(ObjectInputStream stream) 
 473:         throws IOException, ClassNotFoundException {
 474:         stream.defaultReadObject();
 475:         this.arrowPaint = SerialUtilities.readPaint(stream);
 476:         this.arrowStroke = SerialUtilities.readStroke(stream);
 477:     }
 478: 
 479: }