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 }