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 }