001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2021, 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-2021, 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; 051import java.util.Objects; 052 053import org.jfree.chart.HashUtils; 054import org.jfree.chart.axis.ValueAxis; 055import org.jfree.chart.plot.Plot; 056import org.jfree.chart.plot.PlotOrientation; 057import org.jfree.chart.plot.PlotRenderingInfo; 058import org.jfree.chart.plot.XYPlot; 059import org.jfree.chart.ui.RectangleEdge; 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 public double[] getPolygonCoordinates() { 152 return (double[]) this.polygon.clone(); 153 } 154 155 /** 156 * Returns the fill paint. 157 * 158 * @return The fill paint (possibly {@code null}). 159 */ 160 public Paint getFillPaint() { 161 return this.fillPaint; 162 } 163 164 /** 165 * Returns the outline stroke. 166 * 167 * @return The outline stroke (possibly {@code null}). 168 */ 169 public Stroke getOutlineStroke() { 170 return this.stroke; 171 } 172 173 /** 174 * Returns the outline paint. 175 * 176 * @return The outline paint (possibly {@code null}). 177 */ 178 public Paint getOutlinePaint() { 179 return this.outlinePaint; 180 } 181 182 /** 183 * Draws the annotation. This method is usually called by the 184 * {@link XYPlot} class, you shouldn't need to call it directly. 185 * 186 * @param g2 the graphics device. 187 * @param plot the plot. 188 * @param dataArea the data area. 189 * @param domainAxis the domain axis. 190 * @param rangeAxis the range axis. 191 * @param rendererIndex the renderer index. 192 * @param info the plot rendering info. 193 */ 194 @Override 195 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 196 ValueAxis domainAxis, ValueAxis rangeAxis, 197 int rendererIndex, PlotRenderingInfo info) { 198 199 // if we don't have at least 2 (x, y) coordinates, just return 200 if (this.polygon.length < 4) { 201 return; 202 } 203 PlotOrientation orientation = plot.getOrientation(); 204 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 205 plot.getDomainAxisLocation(), orientation); 206 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 207 plot.getRangeAxisLocation(), orientation); 208 209 GeneralPath area = new GeneralPath(); 210 double x = domainAxis.valueToJava2D(this.polygon[0], dataArea, 211 domainEdge); 212 double y = rangeAxis.valueToJava2D(this.polygon[1], dataArea, 213 rangeEdge); 214 if (orientation == PlotOrientation.HORIZONTAL) { 215 area.moveTo((float) y, (float) x); 216 for (int i = 2; i < this.polygon.length; i += 2) { 217 x = domainAxis.valueToJava2D(this.polygon[i], dataArea, 218 domainEdge); 219 y = rangeAxis.valueToJava2D(this.polygon[i + 1], dataArea, 220 rangeEdge); 221 area.lineTo((float) y, (float) x); 222 } 223 area.closePath(); 224 } 225 else if (orientation == PlotOrientation.VERTICAL) { 226 area.moveTo((float) x, (float) y); 227 for (int i = 2; i < this.polygon.length; i += 2) { 228 x = domainAxis.valueToJava2D(this.polygon[i], dataArea, 229 domainEdge); 230 y = rangeAxis.valueToJava2D(this.polygon[i + 1], dataArea, 231 rangeEdge); 232 area.lineTo((float) x, (float) y); 233 } 234 area.closePath(); 235 } 236 237 238 if (this.fillPaint != null) { 239 g2.setPaint(this.fillPaint); 240 g2.fill(area); 241 } 242 243 if (this.stroke != null && this.outlinePaint != null) { 244 g2.setPaint(this.outlinePaint); 245 g2.setStroke(this.stroke); 246 g2.draw(area); 247 } 248 addEntity(info, area, rendererIndex, getToolTipText(), getURL()); 249 250 } 251 252 /** 253 * Tests this annotation for equality with an arbitrary object. 254 * 255 * @param obj the object ({@code null} permitted). 256 * 257 * @return A boolean. 258 */ 259 @Override 260 public boolean equals(Object obj) { 261 if (obj == this) { 262 return true; 263 } 264 // now try to reject equality 265 if (!super.equals(obj)) { 266 return false; 267 } 268 if (!(obj instanceof XYPolygonAnnotation)) { 269 return false; 270 } 271 XYPolygonAnnotation that = (XYPolygonAnnotation) obj; 272 if (!Arrays.equals(this.polygon, that.polygon)) { 273 return false; 274 } 275 if (!Objects.equals(this.stroke, that.stroke)) { 276 return false; 277 } 278 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 279 return false; 280 } 281 if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) { 282 return false; 283 } 284 // seem to be the same 285 return true; 286 } 287 288 /** 289 * Returns a hash code for this instance. 290 * 291 * @return A hash code. 292 */ 293 @Override 294 public int hashCode() { 295 int result = 193; 296 result = 37 * result + HashUtils.hashCodeForDoubleArray( 297 this.polygon); 298 result = 37 * result + HashUtils.hashCodeForPaint(this.fillPaint); 299 result = 37 * result + HashUtils.hashCodeForPaint( 300 this.outlinePaint); 301 if (this.stroke != null) { 302 result = 37 * result + this.stroke.hashCode(); 303 } 304 return result; 305 } 306 307 /** 308 * Returns a clone. 309 * 310 * @return A clone. 311 * 312 * @throws CloneNotSupportedException not thrown by this class, but may be 313 * by subclasses. 314 */ 315 @Override 316 public Object clone() throws CloneNotSupportedException { 317 return super.clone(); 318 } 319 320 /** 321 * Provides serialization support. 322 * 323 * @param stream the output stream ({@code null} not permitted). 324 * 325 * @throws IOException if there is an I/O error. 326 */ 327 private void writeObject(ObjectOutputStream stream) throws IOException { 328 stream.defaultWriteObject(); 329 SerialUtils.writeStroke(this.stroke, stream); 330 SerialUtils.writePaint(this.outlinePaint, stream); 331 SerialUtils.writePaint(this.fillPaint, stream); 332 } 333 334 /** 335 * Provides serialization support. 336 * 337 * @param stream the input stream ({@code null} not permitted). 338 * 339 * @throws IOException if there is an I/O error. 340 * @throws ClassNotFoundException if there is a classpath problem. 341 */ 342 private void readObject(ObjectInputStream stream) 343 throws IOException, ClassNotFoundException { 344 stream.defaultReadObject(); 345 this.stroke = SerialUtils.readStroke(stream); 346 this.outlinePaint = SerialUtils.readPaint(stream); 347 this.fillPaint = SerialUtils.readPaint(stream); 348 } 349 350}