001 /* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2006, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ------------------------ 028 * XYPointerAnnotation.java 029 * ------------------------ 030 * (C) Copyright 2003-2006, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * $Id: XYPointerAnnotation.java,v 1.4.2.4 2006/10/02 16:28:26 mungady Exp $ 036 * 037 * Changes: 038 * -------- 039 * 21-May-2003 : Version 1 (DG); 040 * 10-Jun-2003 : Changed BoundsAnchor to TextAnchor (DG); 041 * 02-Jul-2003 : Added accessor methods and simplified constructor (DG); 042 * 19-Aug-2003 : Implemented Cloneable (DG); 043 * 13-Oct-2003 : Fixed bug where arrow paint is not set correctly (DG); 044 * 21-Jan-2004 : Update for renamed method in ValueAxis (DG); 045 * 29-Sep-2004 : Changes to draw() method signature (DG); 046 * ------------- JFREECHART 1.0.0 --------------------------------------------- 047 * 20-Feb-2006 : Correction for equals() method (fixes bug 1435160) (DG); 048 * 12-Jul-2006 : Fix drawing for PlotOrientation.HORIZONTAL, thanks to 049 * Skunk (DG); 050 * 051 */ 052 053 package org.jfree.chart.annotations; 054 055 import java.awt.BasicStroke; 056 import java.awt.Color; 057 import java.awt.Graphics2D; 058 import java.awt.Paint; 059 import java.awt.Stroke; 060 import java.awt.geom.GeneralPath; 061 import java.awt.geom.Line2D; 062 import java.awt.geom.Rectangle2D; 063 import java.io.IOException; 064 import java.io.ObjectInputStream; 065 import java.io.ObjectOutputStream; 066 import java.io.Serializable; 067 068 import org.jfree.chart.axis.ValueAxis; 069 import org.jfree.chart.plot.Plot; 070 import org.jfree.chart.plot.PlotOrientation; 071 import org.jfree.chart.plot.PlotRenderingInfo; 072 import org.jfree.chart.plot.XYPlot; 073 import org.jfree.io.SerialUtilities; 074 import org.jfree.text.TextUtilities; 075 import org.jfree.ui.RectangleEdge; 076 import org.jfree.util.ObjectUtilities; 077 import org.jfree.util.PublicCloneable; 078 079 /** 080 * An arrow and label that can be placed on an 081 * {@link org.jfree.chart.plot.XYPlot}. The arrow is drawn at a user-definable 082 * angle so that it points towards the (x, y) location for the annotation. 083 * <p> 084 * The arrow length (and its offset from the (x, y) location) is controlled by 085 * the tip radius and the base radius attributes. Imagine two circles around 086 * the (x, y) coordinate: the inner circle defined by the tip radius, and the 087 * outer circle defined by the base radius. Now, draw the arrow starting at 088 * some point on the outer circle (the point is determined by the angle), with 089 * the arrow tip being drawn at a corresponding point on the inner circle. 090 * 091 */ 092 public class XYPointerAnnotation extends XYTextAnnotation 093 implements Cloneable, PublicCloneable, 094 Serializable { 095 096 /** For serialization. */ 097 private static final long serialVersionUID = -4031161445009858551L; 098 099 /** 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 * Returns 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 * Returns 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 * Returns 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 if (paint == null) { 312 throw new IllegalArgumentException("Null 'paint' argument."); 313 } 314 this.arrowPaint = paint; 315 } 316 317 /** 318 * Draws the annotation. 319 * 320 * @param g2 the graphics device. 321 * @param plot the plot. 322 * @param dataArea the data area. 323 * @param domainAxis the domain axis. 324 * @param rangeAxis the range axis. 325 * @param rendererIndex the renderer index. 326 * @param info the plot rendering info. 327 */ 328 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 329 ValueAxis domainAxis, ValueAxis rangeAxis, 330 int rendererIndex, 331 PlotRenderingInfo info) { 332 333 PlotOrientation orientation = plot.getOrientation(); 334 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 335 plot.getDomainAxisLocation(), orientation); 336 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 337 plot.getRangeAxisLocation(), orientation); 338 double j2DX = domainAxis.valueToJava2D(getX(), dataArea, domainEdge); 339 double j2DY = rangeAxis.valueToJava2D(getY(), dataArea, rangeEdge); 340 if (orientation == PlotOrientation.HORIZONTAL) { 341 double temp = j2DX; 342 j2DX = j2DY; 343 j2DY = temp; 344 } 345 double startX = j2DX + Math.cos(this.angle) * this.baseRadius; 346 double startY = j2DY + Math.sin(this.angle) * this.baseRadius; 347 348 double endX = j2DX + Math.cos(this.angle) * this.tipRadius; 349 double endY = j2DY + Math.sin(this.angle) * this.tipRadius; 350 351 double arrowBaseX = endX + Math.cos(this.angle) * this.arrowLength; 352 double arrowBaseY = endY + Math.sin(this.angle) * this.arrowLength; 353 354 double arrowLeftX = arrowBaseX 355 + Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; 356 double arrowLeftY = arrowBaseY 357 + Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; 358 359 double arrowRightX = arrowBaseX 360 - Math.cos(this.angle + Math.PI / 2.0) * this.arrowWidth; 361 double arrowRightY = arrowBaseY 362 - Math.sin(this.angle + Math.PI / 2.0) * this.arrowWidth; 363 364 GeneralPath arrow = new GeneralPath(); 365 arrow.moveTo((float) endX, (float) endY); 366 arrow.lineTo((float) arrowLeftX, (float) arrowLeftY); 367 arrow.lineTo((float) arrowRightX, (float) arrowRightY); 368 arrow.closePath(); 369 370 g2.setStroke(this.arrowStroke); 371 g2.setPaint(this.arrowPaint); 372 Line2D line = new Line2D.Double(startX, startY, endX, endY); 373 g2.draw(line); 374 g2.fill(arrow); 375 376 // draw the label 377 g2.setFont(getFont()); 378 g2.setPaint(getPaint()); 379 double labelX = j2DX 380 + Math.cos(this.angle) * (this.baseRadius + this.labelOffset); 381 double labelY = j2DY 382 + Math.sin(this.angle) * (this.baseRadius + this.labelOffset); 383 Rectangle2D hotspot = TextUtilities.drawAlignedString(getText(), 384 g2, (float) labelX, (float) labelY, getTextAnchor()); 385 386 String toolTip = getToolTipText(); 387 String url = getURL(); 388 if (toolTip != null || url != null) { 389 addEntity(info, hotspot, rendererIndex, toolTip, url); 390 } 391 392 } 393 394 /** 395 * Tests this annotation for equality with an arbitrary object. 396 * 397 * @param obj the object (<code>null</code> permitted). 398 * 399 * @return <code>true</code> or <code>false</code>. 400 */ 401 public boolean equals(Object obj) { 402 403 if (obj == null) { 404 return false; 405 } 406 if (obj == this) { 407 return true; 408 } 409 if (!(obj instanceof XYPointerAnnotation)) { 410 return false; 411 } 412 if (!super.equals(obj)) { 413 return false; 414 } 415 XYPointerAnnotation that = (XYPointerAnnotation) obj; 416 if (this.angle != that.angle) { 417 return false; 418 } 419 if (this.tipRadius != that.tipRadius) { 420 return false; 421 } 422 if (this.baseRadius != that.baseRadius) { 423 return false; 424 } 425 if (this.arrowLength != that.arrowLength) { 426 return false; 427 } 428 if (this.arrowWidth != that.arrowWidth) { 429 return false; 430 } 431 if (!this.arrowPaint.equals(that.arrowPaint)) { 432 return false; 433 } 434 if (!ObjectUtilities.equal(this.arrowStroke, that.arrowStroke)) { 435 return false; 436 } 437 if (this.labelOffset != that.labelOffset) { 438 return false; 439 } 440 return true; 441 } 442 443 /** 444 * Returns a clone of the annotation. 445 * 446 * @return A clone. 447 * 448 * @throws CloneNotSupportedException if the annotation can't be cloned. 449 */ 450 public Object clone() throws CloneNotSupportedException { 451 return super.clone(); 452 } 453 454 /** 455 * Provides serialization support. 456 * 457 * @param stream the output stream. 458 * 459 * @throws IOException if there is an I/O error. 460 */ 461 private void writeObject(ObjectOutputStream stream) throws IOException { 462 stream.defaultWriteObject(); 463 SerialUtilities.writePaint(this.arrowPaint, stream); 464 SerialUtilities.writeStroke(this.arrowStroke, stream); 465 } 466 467 /** 468 * Provides serialization support. 469 * 470 * @param stream the input stream. 471 * 472 * @throws IOException if there is an I/O error. 473 * @throws ClassNotFoundException if there is a classpath problem. 474 */ 475 private void readObject(ObjectInputStream stream) 476 throws IOException, ClassNotFoundException { 477 stream.defaultReadObject(); 478 this.arrowPaint = SerialUtilities.readPaint(stream); 479 this.arrowStroke = SerialUtilities.readStroke(stream); 480 } 481 482 }