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