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