001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025     * Other names may be trademarks of their respective owners.]
026     *
027     * ----------------------
028     * XYShapeAnnotation.java
029     * ----------------------
030     * (C) Copyright 2003-2009, by Ondax, Inc. and Contributors.
031     *
032     * Original Author:  Greg Steckman (for Ondax, Inc.);
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Peter Kolb (patch 2809117);
035     *
036     * Changes:
037     * --------
038     * 15-Aug-2003 : Version 1, adapted from
039     *               org.jfree.chart.annotations.XYLineAnnotation (GS);
040     * 21-Jan-2004 : Update for renamed method in ValueAxis (DG);
041     * 20-Apr-2004 : Added new constructor and fixed bug 934258 (DG);
042     * 29-Sep-2004 : Added 'fillPaint' to allow for colored shapes, now extends
043     *               AbstractXYAnnotation to add tool tip and URL support, and
044     *               implemented equals() and Cloneable (DG);
045     * 21-Jan-2005 : Modified constructor for consistency with other
046     *               constructors (DG);
047     * 06-Jun-2005 : Fixed equals() method to handle GradientPaint (DG);
048     * ------------- JFREECHART 1.0.x ---------------------------------------------
049     * 24-Oct-2006 : Calculate AffineTransform on shape's bounding rectangle
050     *               rather than sample points (0, 0) and (1, 1) (DG);
051     * 06-Mar-2007 : Implemented hashCode() (DG);
052     *
053     */
054    
055    package org.jfree.chart.annotations;
056    
057    import java.awt.BasicStroke;
058    import java.awt.Color;
059    import java.awt.Graphics2D;
060    import java.awt.Paint;
061    import java.awt.Shape;
062    import java.awt.Stroke;
063    import java.awt.geom.AffineTransform;
064    import java.awt.geom.Rectangle2D;
065    import java.io.IOException;
066    import java.io.ObjectInputStream;
067    import java.io.ObjectOutputStream;
068    import java.io.Serializable;
069    
070    import org.jfree.chart.HashUtilities;
071    import org.jfree.chart.axis.ValueAxis;
072    import org.jfree.chart.plot.Plot;
073    import org.jfree.chart.plot.PlotOrientation;
074    import org.jfree.chart.plot.PlotRenderingInfo;
075    import org.jfree.chart.plot.XYPlot;
076    import org.jfree.io.SerialUtilities;
077    import org.jfree.ui.RectangleEdge;
078    import org.jfree.util.ObjectUtilities;
079    import org.jfree.util.PaintUtilities;
080    import org.jfree.util.PublicCloneable;
081    
082    /**
083     * A simple <code>Shape</code> annotation that can be placed on an
084     * {@link XYPlot}.  The shape coordinates are specified in data space.
085     */
086    public class XYShapeAnnotation extends AbstractXYAnnotation
087            implements Cloneable, PublicCloneable, Serializable {
088    
089        /** For serialization. */
090        private static final long serialVersionUID = -8553218317600684041L;
091    
092        /** The shape. */
093        private transient Shape shape;
094    
095        /** The stroke used to draw the shape's outline. */
096        private transient Stroke stroke;
097    
098        /** The paint used to draw the shape's outline. */
099        private transient Paint outlinePaint;
100    
101        /** The paint used to fill the shape. */
102        private transient Paint fillPaint;
103    
104        /**
105         * Creates a new annotation (where, by default, the shape is drawn
106         * with a black outline).
107         *
108         * @param shape  the shape (coordinates in data space, <code>null</code>
109         *     not permitted).
110         */
111        public XYShapeAnnotation(Shape shape) {
112            this(shape, new BasicStroke(1.0f), Color.black);
113        }
114    
115        /**
116         * Creates a new annotation where the shape is drawn as an outline using
117         * the specified <code>stroke</code> and <code>outlinePaint</code>.
118         *
119         * @param shape  the shape (<code>null</code> not permitted).
120         * @param stroke  the shape stroke (<code>null</code> permitted).
121         * @param outlinePaint  the shape color (<code>null</code> permitted).
122         */
123        public XYShapeAnnotation(Shape shape, Stroke stroke, Paint outlinePaint) {
124            this(shape, stroke, outlinePaint, null);
125        }
126    
127        /**
128         * Creates a new annotation.
129         *
130         * @param shape  the shape (<code>null</code> not permitted).
131         * @param stroke  the shape stroke (<code>null</code> permitted).
132         * @param outlinePaint  the shape color (<code>null</code> permitted).
133         * @param fillPaint  the paint used to fill the shape (<code>null</code>
134         *                   permitted.
135         */
136        public XYShapeAnnotation(Shape shape, Stroke stroke, Paint outlinePaint,
137                                 Paint fillPaint) {
138            super();
139            if (shape == null) {
140                throw new IllegalArgumentException("Null 'shape' argument.");
141            }
142            this.shape = shape;
143            this.stroke = stroke;
144            this.outlinePaint = outlinePaint;
145            this.fillPaint = fillPaint;
146        }
147    
148        /**
149         * Draws the annotation.  This method is usually called by the
150         * {@link XYPlot} class, you shouldn't need to call it directly.
151         *
152         * @param g2  the graphics device.
153         * @param plot  the plot.
154         * @param dataArea  the data area.
155         * @param domainAxis  the domain axis.
156         * @param rangeAxis  the range axis.
157         * @param rendererIndex  the renderer index.
158         * @param info  the plot rendering info.
159         */
160        public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
161                         ValueAxis domainAxis, ValueAxis rangeAxis,
162                         int rendererIndex,
163                         PlotRenderingInfo info) {
164    
165            PlotOrientation orientation = plot.getOrientation();
166            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
167                    plot.getDomainAxisLocation(), orientation);
168            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
169                    plot.getRangeAxisLocation(), orientation);
170    
171            // compute transform matrix elements via sample points. Assume no
172            // rotation or shear.
173            Rectangle2D bounds = this.shape.getBounds2D();
174            double x0 = bounds.getMinX();
175            double x1 = bounds.getMaxX();
176            double xx0 = domainAxis.valueToJava2D(x0, dataArea, domainEdge);
177            double xx1 = domainAxis.valueToJava2D(x1, dataArea, domainEdge);
178            double m00 = (xx1 - xx0) / (x1 - x0);
179            double m02 = xx0 - x0 * m00;
180    
181            double y0 = bounds.getMaxY();
182            double y1 = bounds.getMinY();
183            double yy0 = rangeAxis.valueToJava2D(y0, dataArea, rangeEdge);
184            double yy1 = rangeAxis.valueToJava2D(y1, dataArea, rangeEdge);
185            double m11 = (yy1 - yy0) / (y1 - y0);
186            double m12 = yy0 - m11 * y0;
187    
188            //  create transform & transform shape
189            Shape s = null;
190            if (orientation == PlotOrientation.HORIZONTAL) {
191                AffineTransform t1 = new AffineTransform(0.0f, 1.0f, 1.0f, 0.0f,
192                        0.0f, 0.0f);
193                AffineTransform t2 = new AffineTransform(m11, 0.0f, 0.0f, m00,
194                        m12, m02);
195                s = t1.createTransformedShape(this.shape);
196                s = t2.createTransformedShape(s);
197            }
198            else if (orientation == PlotOrientation.VERTICAL) {
199                AffineTransform t = new AffineTransform(m00, 0, 0, m11, m02, m12);
200                s = t.createTransformedShape(this.shape);
201            }
202    
203            if (this.fillPaint != null) {
204                g2.setPaint(this.fillPaint);
205                g2.fill(s);
206            }
207    
208            if (this.stroke != null && this.outlinePaint != null) {
209                g2.setPaint(this.outlinePaint);
210                g2.setStroke(this.stroke);
211                g2.draw(s);
212            }
213            addEntity(info, s, rendererIndex, getToolTipText(), getURL());
214    
215        }
216    
217        /**
218         * Tests this annotation for equality with an arbitrary object.
219         *
220         * @param obj  the object (<code>null</code> permitted).
221         *
222         * @return A boolean.
223         */
224        public boolean equals(Object obj) {
225            if (obj == this) {
226                return true;
227            }
228            // now try to reject equality
229            if (!super.equals(obj)) {
230                return false;
231            }
232            if (!(obj instanceof XYShapeAnnotation)) {
233                return false;
234            }
235            XYShapeAnnotation that = (XYShapeAnnotation) obj;
236            if (!this.shape.equals(that.shape)) {
237                return false;
238            }
239            if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
240                return false;
241            }
242            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
243                return false;
244            }
245            if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
246                return false;
247            }
248            // seem to be the same
249            return true;
250        }
251    
252        /**
253         * Returns a hash code for this instance.
254         *
255         * @return A hash code.
256         */
257        public int hashCode() {
258            int result = 193;
259            result = 37 * result + this.shape.hashCode();
260            if (this.stroke != null) {
261                result = 37 * result + this.stroke.hashCode();
262            }
263            result = 37 * result + HashUtilities.hashCodeForPaint(
264                    this.outlinePaint);
265            result = 37 * result + HashUtilities.hashCodeForPaint(this.fillPaint);
266            return result;
267        }
268    
269        /**
270         * Returns a clone.
271         *
272         * @return A clone.
273         *
274         * @throws CloneNotSupportedException ???.
275         */
276        public Object clone() throws CloneNotSupportedException {
277            return super.clone();
278        }
279    
280        /**
281         * Provides serialization support.
282         *
283         * @param stream  the output stream.
284         *
285         * @throws IOException if there is an I/O error.
286         */
287        private void writeObject(ObjectOutputStream stream) throws IOException {
288            stream.defaultWriteObject();
289            SerialUtilities.writeShape(this.shape, stream);
290            SerialUtilities.writeStroke(this.stroke, stream);
291            SerialUtilities.writePaint(this.outlinePaint, stream);
292            SerialUtilities.writePaint(this.fillPaint, stream);
293        }
294    
295        /**
296         * Provides serialization support.
297         *
298         * @param stream  the input stream.
299         *
300         * @throws IOException  if there is an I/O error.
301         * @throws ClassNotFoundException  if there is a classpath problem.
302         */
303        private void readObject(ObjectInputStream stream)
304            throws IOException, ClassNotFoundException {
305            stream.defaultReadObject();
306            this.shape = SerialUtilities.readShape(stream);
307            this.stroke = SerialUtilities.readStroke(stream);
308            this.outlinePaint = SerialUtilities.readPaint(stream);
309            this.fillPaint = SerialUtilities.readPaint(stream);
310        }
311    
312    }