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    }