001: /* ===========================================================
002: * JFreeChart : a free chart library for the Java(tm) platform
003: * ===========================================================
004: *
005: * (C) Copyright 2000-2007, 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-2007, 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.5 2007/03/06 16:12:18 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.x ---------------------------------------------
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.HashUtilities;
069: import org.jfree.chart.axis.ValueAxis;
070: import org.jfree.chart.plot.Plot;
071: import org.jfree.chart.plot.PlotOrientation;
072: import org.jfree.chart.plot.PlotRenderingInfo;
073: import org.jfree.chart.plot.XYPlot;
074: import org.jfree.io.SerialUtilities;
075: import org.jfree.text.TextUtilities;
076: import org.jfree.ui.RectangleEdge;
077: import org.jfree.util.ObjectUtilities;
078: import org.jfree.util.PublicCloneable;
079:
080: /**
081: * An arrow and label that can be placed on an
082: * {@link org.jfree.chart.plot.XYPlot}. The arrow is drawn at a user-definable
083: * angle so that it points towards the (x, y) location for the annotation.
084: * <p>
085: * The arrow length (and its offset from the (x, y) location) is controlled by
086: * the tip radius and the base radius attributes. Imagine two circles around
087: * the (x, y) coordinate: the inner circle defined by the tip radius, and the
088: * outer circle defined by the base radius. Now, draw the arrow starting at
089: * some point on the outer circle (the point is determined by the angle), with
090: * the arrow tip being drawn at a corresponding point on the inner circle.
091: *
092: */
093: public class XYPointerAnnotation extends XYTextAnnotation implements
094: Cloneable, PublicCloneable, 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,
153: 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(
321: "Null 'stroke' not permitted.");
322: }
323: this .arrowStroke = stroke;
324: }
325:
326: /**
327: * Returns the paint used for the arrow.
328: *
329: * @return The arrow paint (never <code>null</code>).
330: *
331: * @see #setArrowPaint(Paint)
332: */
333: public Paint getArrowPaint() {
334: return this .arrowPaint;
335: }
336:
337: /**
338: * Sets the paint used for the arrow.
339: *
340: * @param paint the arrow paint (<code>null</code> not permitted).
341: *
342: * @see #getArrowPaint()
343: */
344: public void setArrowPaint(Paint paint) {
345: if (paint == null) {
346: throw new IllegalArgumentException("Null 'paint' argument.");
347: }
348: this .arrowPaint = paint;
349: }
350:
351: /**
352: * Draws the annotation.
353: *
354: * @param g2 the graphics device.
355: * @param plot the plot.
356: * @param dataArea the data area.
357: * @param domainAxis the domain axis.
358: * @param rangeAxis the range axis.
359: * @param rendererIndex the renderer index.
360: * @param info the plot rendering info.
361: */
362: public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
363: ValueAxis domainAxis, ValueAxis rangeAxis,
364: int rendererIndex, PlotRenderingInfo info) {
365:
366: PlotOrientation orientation = plot.getOrientation();
367: RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(plot
368: .getDomainAxisLocation(), orientation);
369: RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(plot
370: .getRangeAxisLocation(), orientation);
371: double j2DX = domainAxis.valueToJava2D(getX(), dataArea,
372: domainEdge);
373: double j2DY = rangeAxis.valueToJava2D(getY(), dataArea,
374: rangeEdge);
375: if (orientation == PlotOrientation.HORIZONTAL) {
376: double temp = j2DX;
377: j2DX = j2DY;
378: j2DY = temp;
379: }
380: double startX = j2DX + Math.cos(this .angle) * this .baseRadius;
381: double startY = j2DY + Math.sin(this .angle) * this .baseRadius;
382:
383: double endX = j2DX + Math.cos(this .angle) * this .tipRadius;
384: double endY = j2DY + Math.sin(this .angle) * this .tipRadius;
385:
386: double arrowBaseX = endX + Math.cos(this .angle)
387: * this .arrowLength;
388: double arrowBaseY = endY + Math.sin(this .angle)
389: * this .arrowLength;
390:
391: double arrowLeftX = arrowBaseX
392: + Math.cos(this .angle + Math.PI / 2.0)
393: * this .arrowWidth;
394: double arrowLeftY = arrowBaseY
395: + Math.sin(this .angle + Math.PI / 2.0)
396: * this .arrowWidth;
397:
398: double arrowRightX = arrowBaseX
399: - Math.cos(this .angle + Math.PI / 2.0)
400: * this .arrowWidth;
401: double arrowRightY = arrowBaseY
402: - Math.sin(this .angle + Math.PI / 2.0)
403: * this .arrowWidth;
404:
405: GeneralPath arrow = new GeneralPath();
406: arrow.moveTo((float) endX, (float) endY);
407: arrow.lineTo((float) arrowLeftX, (float) arrowLeftY);
408: arrow.lineTo((float) arrowRightX, (float) arrowRightY);
409: arrow.closePath();
410:
411: g2.setStroke(this .arrowStroke);
412: g2.setPaint(this .arrowPaint);
413: Line2D line = new Line2D.Double(startX, startY, endX, endY);
414: g2.draw(line);
415: g2.fill(arrow);
416:
417: // draw the label
418: g2.setFont(getFont());
419: g2.setPaint(getPaint());
420: double labelX = j2DX + Math.cos(this .angle)
421: * (this .baseRadius + this .labelOffset);
422: double labelY = j2DY + Math.sin(this .angle)
423: * (this .baseRadius + this .labelOffset);
424: Rectangle2D hotspot = TextUtilities.drawAlignedString(
425: getText(), g2, (float) labelX, (float) labelY,
426: getTextAnchor());
427:
428: String toolTip = getToolTipText();
429: String url = getURL();
430: if (toolTip != null || url != null) {
431: addEntity(info, hotspot, rendererIndex, toolTip, url);
432: }
433:
434: }
435:
436: /**
437: * Tests this annotation for equality with an arbitrary object.
438: *
439: * @param obj the object (<code>null</code> permitted).
440: *
441: * @return <code>true</code> or <code>false</code>.
442: */
443: public boolean equals(Object obj) {
444: if (obj == this ) {
445: return true;
446: }
447: if (!(obj instanceof XYPointerAnnotation)) {
448: return false;
449: }
450: if (!super .equals(obj)) {
451: return false;
452: }
453: XYPointerAnnotation that = (XYPointerAnnotation) obj;
454: if (this .angle != that.angle) {
455: return false;
456: }
457: if (this .tipRadius != that.tipRadius) {
458: return false;
459: }
460: if (this .baseRadius != that.baseRadius) {
461: return false;
462: }
463: if (this .arrowLength != that.arrowLength) {
464: return false;
465: }
466: if (this .arrowWidth != that.arrowWidth) {
467: return false;
468: }
469: if (!this .arrowPaint.equals(that.arrowPaint)) {
470: return false;
471: }
472: if (!ObjectUtilities.equal(this .arrowStroke, that.arrowStroke)) {
473: return false;
474: }
475: if (this .labelOffset != that.labelOffset) {
476: return false;
477: }
478: return true;
479: }
480:
481: /**
482: * Returns a hash code for this instance.
483: *
484: * @return A hash code.
485: */
486: public int hashCode() {
487: int result = super .hashCode();
488: long temp = Double.doubleToLongBits(this .angle);
489: result = 37 * result + (int) (temp ^ (temp >>> 32));
490: temp = Double.doubleToLongBits(this .tipRadius);
491: result = 37 * result + (int) (temp ^ (temp >>> 32));
492: temp = Double.doubleToLongBits(this .baseRadius);
493: result = 37 * result + (int) (temp ^ (temp >>> 32));
494: temp = Double.doubleToLongBits(this .arrowLength);
495: result = 37 * result + (int) (temp ^ (temp >>> 32));
496: temp = Double.doubleToLongBits(this .arrowWidth);
497: result = 37 * result + (int) (temp ^ (temp >>> 32));
498: result = result * 37
499: + HashUtilities.hashCodeForPaint(this .arrowPaint);
500: result = result * 37 + this .arrowStroke.hashCode();
501: temp = Double.doubleToLongBits(this .labelOffset);
502: result = 37 * result + (int) (temp ^ (temp >>> 32));
503: return super .hashCode();
504: }
505:
506: /**
507: * Returns a clone of the annotation.
508: *
509: * @return A clone.
510: *
511: * @throws CloneNotSupportedException if the annotation can't be cloned.
512: */
513: public Object clone() throws CloneNotSupportedException {
514: return super .clone();
515: }
516:
517: /**
518: * Provides serialization support.
519: *
520: * @param stream the output stream.
521: *
522: * @throws IOException if there is an I/O error.
523: */
524: private void writeObject(ObjectOutputStream stream)
525: throws IOException {
526: stream.defaultWriteObject();
527: SerialUtilities.writePaint(this .arrowPaint, stream);
528: SerialUtilities.writeStroke(this .arrowStroke, stream);
529: }
530:
531: /**
532: * Provides serialization support.
533: *
534: * @param stream the input stream.
535: *
536: * @throws IOException if there is an I/O error.
537: * @throws ClassNotFoundException if there is a classpath problem.
538: */
539: private void readObject(ObjectInputStream stream)
540: throws IOException, ClassNotFoundException {
541: stream.defaultReadObject();
542: this.arrowPaint = SerialUtilities.readPaint(stream);
543: this.arrowStroke = SerialUtilities.readStroke(stream);
544: }
545:
546: }
|