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     * XYPolygonAnnotation.java
029     * ------------------------
030     * (C) Copyright 2005-2007, by Object Refinery Limited and Contributors.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   -;
034     *
035     * Changes:
036     * --------
037     * 09-Feb-2005 : Version 1 (DG);
038     * 
039     */
040     
041    package org.jfree.chart.annotations;
042    
043    import java.awt.BasicStroke;
044    import java.awt.Color;
045    import java.awt.Graphics2D;
046    import java.awt.Paint;
047    import java.awt.Stroke;
048    import java.awt.geom.GeneralPath;
049    import java.awt.geom.Rectangle2D;
050    import java.io.IOException;
051    import java.io.ObjectInputStream;
052    import java.io.ObjectOutputStream;
053    import java.io.Serializable;
054    import java.util.Arrays;
055    
056    import org.jfree.chart.HashUtilities;
057    import org.jfree.chart.axis.ValueAxis;
058    import org.jfree.chart.plot.Plot;
059    import org.jfree.chart.plot.PlotOrientation;
060    import org.jfree.chart.plot.PlotRenderingInfo;
061    import org.jfree.chart.plot.XYPlot;
062    import org.jfree.io.SerialUtilities;
063    import org.jfree.ui.RectangleEdge;
064    import org.jfree.util.ObjectUtilities;
065    import org.jfree.util.PaintUtilities;
066    import org.jfree.util.PublicCloneable;
067    
068    /**
069     * A polygon annotation that can be placed on an {@link XYPlot}.  The 
070     * polygon coordinates are specified in data space.
071     */
072    public class XYPolygonAnnotation extends AbstractXYAnnotation
073                                     implements Cloneable, 
074                                                PublicCloneable, 
075                                                Serializable {
076        
077        /** For serialization. */
078        private static final long serialVersionUID = -6984203651995900036L;
079        
080        /** The polygon. */
081        private double[] polygon;
082    
083        /** The stroke used to draw the box outline. */
084        private transient Stroke stroke;
085    
086        /** The paint used to draw the box outline. */
087        private transient Paint outlinePaint;
088        
089        /** The paint used to fill the box. */
090        private transient Paint fillPaint;
091    
092        /**
093         * Creates a new annotation (where, by default, the polygon is drawn 
094         * with a black outline).  The array of polygon coordinates must contain
095         * an even number of coordinates (each pair is an (x, y) location on the
096         * plot) and the last point is automatically joined back to the first point.
097         * 
098         * @param polygon  the coordinates of the polygon's vertices 
099         *     (<code>null</code> not permitted).
100         */
101        public XYPolygonAnnotation(double[] polygon) {
102            this(polygon, new BasicStroke(1.0f), Color.black);
103        }
104        
105        /**
106         * Creates a new annotation where the box is drawn as an outline using
107         * the specified <code>stroke</code> and <code>outlinePaint</code>.  
108         * The array of polygon coordinates must contain an even number of 
109         * coordinates (each pair is an (x, y) location on the plot) and the last 
110         * point is automatically joined back to the first point.
111         *
112         * @param polygon  the coordinates of the polygon's vertices 
113         *     (<code>null</code> not permitted).
114         * @param stroke  the shape stroke (<code>null</code> permitted).
115         * @param outlinePaint  the shape color (<code>null</code> permitted).
116         */
117        public XYPolygonAnnotation(double[] polygon, 
118                                   Stroke stroke, Paint outlinePaint) {
119            this(polygon, stroke, outlinePaint, null);
120        }
121    
122        /**
123         * Creates a new annotation.  The array of polygon coordinates must 
124         * contain an even number of coordinates (each pair is an (x, y) location 
125         * on the plot) and the last point is automatically joined back to the 
126         * first point.
127         *
128         * @param polygon  the coordinates of the polygon's vertices 
129         *     (<code>null</code> not permitted).
130         * @param stroke  the shape stroke (<code>null</code> permitted).
131         * @param outlinePaint  the shape color (<code>null</code> permitted).
132         * @param fillPaint  the paint used to fill the shape (<code>null</code> 
133         *                   permitted).
134         */
135        public XYPolygonAnnotation(double[] polygon, 
136                                   Stroke stroke, 
137                                   Paint outlinePaint, Paint fillPaint) {
138            if (polygon == null) {
139                throw new IllegalArgumentException("Null 'polygon' argument.");
140            }
141            if (polygon.length % 2 != 0) {
142                throw new IllegalArgumentException("The 'polygon' array must " 
143                        + "contain an even number of items.");
144            }
145            this.polygon = (double[]) polygon.clone();
146            this.stroke = stroke;
147            this.outlinePaint = outlinePaint;
148            this.fillPaint = fillPaint;
149        }
150        
151        /**
152         * Returns the coordinates of the polygon's vertices.  The returned array
153         * is a copy, so it is safe to modify without altering the annotation's 
154         * state.
155         * 
156         * @return The coordinates of the polygon's vertices.
157         * 
158         * @since 1.0.2
159         */
160        public double[] getPolygonCoordinates() {
161            return (double[]) this.polygon.clone();
162        }
163        
164        /**
165         * Returns the fill paint.
166         * 
167         * @return The fill paint (possibly <code>null</code>).
168         * 
169         * @since 1.0.2
170         */
171        public Paint getFillPaint() {
172            return this.fillPaint;
173        }
174        
175        /**
176         * Returns the outline stroke.
177         * 
178         * @return The outline stroke (possibly <code>null</code>).
179         * 
180         * @since 1.0.2
181         */
182        public Stroke getOutlineStroke() {
183            return this.stroke;
184        }
185        
186        /**
187         * Returns the outline paint.
188         * 
189         * @return The outline paint (possibly <code>null</code>).
190         * 
191         * @since 1.0.2
192         */
193        public Paint getOutlinePaint() {
194            return this.outlinePaint;
195        }
196    
197        /**
198         * Draws the annotation.  This method is usually called by the 
199         * {@link XYPlot} class, you shouldn't need to call it directly.
200         *
201         * @param g2  the graphics device.
202         * @param plot  the plot.
203         * @param dataArea  the data area.
204         * @param domainAxis  the domain axis.
205         * @param rangeAxis  the range axis.
206         * @param rendererIndex  the renderer index.
207         * @param info  the plot rendering info.
208         */
209        public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea,
210                         ValueAxis domainAxis, ValueAxis rangeAxis, 
211                         int rendererIndex, PlotRenderingInfo info) {
212    
213            // if we don't have at least 2 (x, y) coordinates, just return
214            if (this.polygon.length < 4) {
215                return;
216            }
217            PlotOrientation orientation = plot.getOrientation();
218            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(
219                    plot.getDomainAxisLocation(), orientation);
220            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(
221                    plot.getRangeAxisLocation(), orientation);
222    
223            GeneralPath area = new GeneralPath();
224            double x = domainAxis.valueToJava2D(this.polygon[0], dataArea, 
225                    domainEdge);
226            double y = rangeAxis.valueToJava2D(this.polygon[1], dataArea, 
227                    rangeEdge);
228            if (orientation == PlotOrientation.HORIZONTAL) {
229                area.moveTo((float) y, (float) x);
230                for (int i = 2; i < this.polygon.length; i += 2) {
231                    x = domainAxis.valueToJava2D(this.polygon[i], dataArea, 
232                            domainEdge);
233                    y = rangeAxis.valueToJava2D(this.polygon[i + 1], dataArea, 
234                            rangeEdge);
235                    area.lineTo((float) y, (float) x);
236                }
237                area.closePath();
238            }
239            else if (orientation == PlotOrientation.VERTICAL) {
240                area.moveTo((float) x, (float) y);            
241                for (int i = 2; i < this.polygon.length; i += 2) {
242                    x = domainAxis.valueToJava2D(this.polygon[i], dataArea, 
243                            domainEdge);
244                    y = rangeAxis.valueToJava2D(this.polygon[i + 1], dataArea, 
245                            rangeEdge);
246                    area.lineTo((float) x, (float) y);
247                }
248                area.closePath();
249           }
250            
251    
252            if (this.fillPaint != null) {
253                g2.setPaint(this.fillPaint);
254                g2.fill(area);
255            }
256            
257            if (this.stroke != null && this.outlinePaint != null) {
258                g2.setPaint(this.outlinePaint);
259                g2.setStroke(this.stroke);
260                g2.draw(area);
261            }
262            addEntity(info, area, rendererIndex, getToolTipText(), getURL());
263            
264        }
265            
266        /**
267         * Tests this annotation for equality with an arbitrary object.
268         * 
269         * @param obj  the object (<code>null</code> permitted).
270         * 
271         * @return A boolean.
272         */
273        public boolean equals(Object obj) {
274            if (obj == this) {
275                return true;
276            }
277            // now try to reject equality
278            if (!super.equals(obj)) {
279                return false;
280            }
281            if (!(obj instanceof XYPolygonAnnotation)) {
282                return false;
283            }
284            XYPolygonAnnotation that = (XYPolygonAnnotation) obj;
285            if (!Arrays.equals(this.polygon, that.polygon)) {
286                return false;   
287            }
288            if (!ObjectUtilities.equal(this.stroke, that.stroke)) {
289                return false;
290            }
291            if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
292                return false;
293            }
294            if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
295                return false;
296            }
297            // seem to be the same
298            return true;
299        }
300        
301        /**
302         * Returns a hash code for this instance.
303         * 
304         * @return A hash code.
305         */
306        public int hashCode() {
307            int result = 193;
308            result = 37 * result + HashUtilities.hashCodeForDoubleArray(
309                    this.polygon);
310            result = 37 * result + HashUtilities.hashCodeForPaint(this.fillPaint);
311            result = 37 * result + HashUtilities.hashCodeForPaint(
312                    this.outlinePaint);
313            if (this.stroke != null) {
314                result = 37 * result + this.stroke.hashCode();
315            }
316            return result;
317        }
318        
319        /**
320         * Returns a clone.
321         * 
322         * @return A clone.
323         * 
324         * @throws CloneNotSupportedException not thrown by this class, but may be
325         *                                    by subclasses.
326         */
327        public Object clone() throws CloneNotSupportedException {
328            return super.clone();
329        }
330        
331        /**
332         * Provides serialization support.
333         *
334         * @param stream  the output stream (<code>null</code> not permitted).
335         *
336         * @throws IOException if there is an I/O error.
337         */
338        private void writeObject(ObjectOutputStream stream) throws IOException {
339            stream.defaultWriteObject();
340            SerialUtilities.writeStroke(this.stroke, stream);
341            SerialUtilities.writePaint(this.outlinePaint, stream);
342            SerialUtilities.writePaint(this.fillPaint, stream);
343        }
344    
345        /**
346         * Provides serialization support.
347         *
348         * @param stream  the input stream (<code>null</code> not permitted).
349         *
350         * @throws IOException  if there is an I/O error.
351         * @throws ClassNotFoundException  if there is a classpath problem.
352         */
353        private void readObject(ObjectInputStream stream) 
354                throws IOException, ClassNotFoundException {
355            stream.defaultReadObject();
356            this.stroke = SerialUtilities.readStroke(stream);
357            this.outlinePaint = SerialUtilities.readPaint(stream);
358            this.fillPaint = SerialUtilities.readPaint(stream);
359        }
360    
361    }