Frames | No Frames |
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: }