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}