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