001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert 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 * XYBoxAnnotation.java 029 * -------------------- 030 * (C) Copyright 2005-present, by David Gilbert and Contributors. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Peter Kolb (see patch 2809117); 034 Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier); 035 * 036 */ 037 038package org.jfree.chart.annotations; 039 040import java.awt.BasicStroke; 041import java.awt.Color; 042import java.awt.Graphics2D; 043import java.awt.Paint; 044import java.awt.Stroke; 045import java.awt.geom.Rectangle2D; 046import java.io.IOException; 047import java.io.ObjectInputStream; 048import java.io.ObjectOutputStream; 049import java.io.Serializable; 050import java.util.Objects; 051import org.jfree.chart.HashUtils; 052 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.Args; 060import org.jfree.chart.util.PaintUtils; 061import org.jfree.chart.util.PublicCloneable; 062import org.jfree.chart.util.SerialUtils; 063 064/** 065 * A box annotation that can be placed on an {@link XYPlot}. The 066 * box coordinates are specified in data space. 067 */ 068public class XYBoxAnnotation extends AbstractXYAnnotation 069 implements Cloneable, PublicCloneable, Serializable { 070 071 /** For serialization. */ 072 private static final long serialVersionUID = 6764703772526757457L; 073 074 /** The lower x-coordinate. */ 075 private double x0; 076 077 /** The lower y-coordinate. */ 078 private double y0; 079 080 /** The upper x-coordinate. */ 081 private double x1; 082 083 /** The upper y-coordinate. */ 084 private double y1; 085 086 /** The stroke used to draw the box outline. */ 087 private transient Stroke stroke; 088 089 /** The paint used to draw the box outline. */ 090 private transient Paint outlinePaint; 091 092 /** The paint used to fill the box. */ 093 private transient Paint fillPaint; 094 095 /** 096 * Creates a new annotation (where, by default, the box is drawn 097 * with a black outline). 098 * 099 * @param x0 the lower x-coordinate of the box (in data space). 100 * @param y0 the lower y-coordinate of the box (in data space). 101 * @param x1 the upper x-coordinate of the box (in data space). 102 * @param y1 the upper y-coordinate of the box (in data space). 103 */ 104 public XYBoxAnnotation(double x0, double y0, double x1, double y1) { 105 this(x0, y0, x1, y1, new BasicStroke(1.0f), Color.BLACK); 106 } 107 108 /** 109 * Creates a new annotation where the box is drawn as an outline using 110 * the specified {@code stroke} and {@code outlinePaint}. 111 * 112 * @param x0 the lower x-coordinate of the box (in data space). 113 * @param y0 the lower y-coordinate of the box (in data space). 114 * @param x1 the upper x-coordinate of the box (in data space). 115 * @param y1 the upper y-coordinate of the box (in data space). 116 * @param stroke the shape stroke ({@code null} permitted). 117 * @param outlinePaint the shape color ({@code null} permitted). 118 */ 119 public XYBoxAnnotation(double x0, double y0, double x1, double y1, 120 Stroke stroke, Paint outlinePaint) { 121 this(x0, y0, x1, y1, stroke, outlinePaint, null); 122 } 123 124 /** 125 * Creates a new annotation. 126 * 127 * @param x0 the lower x-coordinate of the box (in data space, must be finite). 128 * @param y0 the lower y-coordinate of the box (in data space, must be finite). 129 * @param x1 the upper x-coordinate of the box (in data space, must be finite). 130 * @param y1 the upper y-coordinate of the box (in data space, must be finite). 131 * @param stroke the shape stroke ({@code null} permitted). 132 * @param outlinePaint the shape color ({@code null} permitted). 133 * @param fillPaint the paint used to fill the shape ({@code null} 134 * permitted). 135 */ 136 public XYBoxAnnotation(double x0, double y0, double x1, double y1, 137 Stroke stroke, Paint outlinePaint, Paint fillPaint) { 138 super(); 139 Args.requireFinite(x0, "x0"); 140 Args.requireFinite(y0, "y0"); 141 Args.requireFinite(x1, "x1"); 142 Args.requireFinite(y1, "y1"); 143 this.x0 = x0; 144 this.y0 = y0; 145 this.x1 = x1; 146 this.y1 = y1; 147 this.stroke = stroke; 148 this.outlinePaint = outlinePaint; 149 this.fillPaint = fillPaint; 150 } 151 152 /** 153 * Returns the x-coordinate for the bottom left corner of the box (set in the 154 * constructor). 155 * 156 * @return The x-coordinate for the bottom left corner of the box. 157 */ 158 public double getX0() { 159 return x0; 160 } 161 162 /** 163 * Returns the y-coordinate for the bottom left corner of the box (set in the 164 * constructor). 165 * 166 * @return The y-coordinate for the bottom left corner of the box. 167 */ 168 public double getY0() { 169 return y0; 170 } 171 172 /** 173 * Returns the x-coordinate for the top right corner of the box (set in the 174 * constructor). 175 * 176 * @return The x-coordinate for the top right corner of the box. 177 */ 178 public double getX1() { 179 return x1; 180 } 181 182 /** 183 * Returns the y-coordinate for the top right corner of the box (set in the 184 * constructor). 185 * 186 * @return The y-coordinate for the top right corner of the box. 187 */ 188 public double getY1() { 189 return y1; 190 } 191 192 /** 193 * Returns the stroke used for the box outline. 194 * 195 * @return The stroke (possibly {@code null}). 196 */ 197 public Stroke getStroke() { 198 return stroke; 199 } 200 201 /** 202 * Returns the paint used for the box outline. 203 * 204 * @return The paint (possibly {@code null}). 205 */ 206 public Paint getOutlinePaint() { 207 return outlinePaint; 208 } 209 210 /** 211 * Returns the paint used for the box fill. 212 * 213 * @return The paint (possibly {@code null}). 214 */ 215 public Paint getFillPaint() { 216 return fillPaint; 217 } 218 219 /** 220 * Draws the annotation. This method is usually called by the 221 * {@link XYPlot} class, you shouldn't need to call it directly. 222 * 223 * @param g2 the graphics device. 224 * @param plot the plot. 225 * @param dataArea the data area. 226 * @param domainAxis the domain axis. 227 * @param rangeAxis the range axis. 228 * @param rendererIndex the renderer index. 229 * @param info the plot rendering info. 230 */ 231 @Override 232 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 233 ValueAxis domainAxis, ValueAxis rangeAxis, 234 int rendererIndex, PlotRenderingInfo info) { 235 236 PlotOrientation orientation = plot.getOrientation(); 237 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 238 plot.getDomainAxisLocation(), orientation); 239 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 240 plot.getRangeAxisLocation(), orientation); 241 242 double transX0 = domainAxis.valueToJava2D(this.x0, dataArea, 243 domainEdge); 244 double transY0 = rangeAxis.valueToJava2D(this.y0, dataArea, rangeEdge); 245 double transX1 = domainAxis.valueToJava2D(this.x1, dataArea, 246 domainEdge); 247 double transY1 = rangeAxis.valueToJava2D(this.y1, dataArea, rangeEdge); 248 249 Rectangle2D box = null; 250 if (orientation == PlotOrientation.HORIZONTAL) { 251 box = new Rectangle2D.Double(transY0, transX1, transY1 - transY0, 252 transX0 - transX1); 253 } else if (orientation == PlotOrientation.VERTICAL) { 254 box = new Rectangle2D.Double(transX0, transY1, transX1 - transX0, 255 transY0 - transY1); 256 } 257 258 if (this.fillPaint != null) { 259 g2.setPaint(this.fillPaint); 260 g2.fill(box); 261 } 262 263 if (this.stroke != null && this.outlinePaint != null) { 264 g2.setPaint(this.outlinePaint); 265 g2.setStroke(this.stroke); 266 g2.draw(box); 267 } 268 addEntity(info, box, rendererIndex, getToolTipText(), getURL()); 269 } 270 271 /** 272 * Tests this annotation for equality with an arbitrary object. 273 * 274 * @param obj the object ({@code null} permitted). 275 * 276 * @return A boolean. 277 */ 278 @Override 279 public boolean equals(Object obj) { 280 if (obj == this) { 281 return true; 282 } 283 if (!(obj instanceof XYBoxAnnotation)) { 284 return false; 285 } 286 XYBoxAnnotation that = (XYBoxAnnotation) obj; 287 if (Double.doubleToLongBits(this.x0) != 288 Double.doubleToLongBits(that.x0)) { 289 return false; 290 } 291 if (Double.doubleToLongBits(this.y0) != 292 Double.doubleToLongBits(that.y0)) { 293 return false; 294 } 295 if (Double.doubleToLongBits(this.x1) != 296 Double.doubleToLongBits(that.x1)) { 297 return false; 298 } 299 if (Double.doubleToLongBits(this.y1) != 300 Double.doubleToLongBits(that.y1)) { 301 return false; 302 } 303 if (!Objects.equals(this.stroke, that.stroke)) { 304 return false; 305 } 306 if (!PaintUtils.equal(this.outlinePaint, that.outlinePaint)) { 307 return false; 308 } 309 if (!PaintUtils.equal(this.fillPaint, that.fillPaint)) { 310 return false; 311 } 312 // fix the "equals not symmetric" problem 313 if (!that.canEqual(this)) { 314 return false; 315 } 316 return super.equals(obj); 317 } 318 319 /** 320 * Ensures symmetry between super/subclass implementations of equals. For 321 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 322 * 323 * @param other Object 324 * 325 * @return true ONLY if the parameter is THIS class type 326 */ 327 @Override 328 public boolean canEqual(Object other) { 329 // fix the "equals not symmetric" problem 330 return (other instanceof XYBoxAnnotation); 331 } 332 333 /** 334 * Returns a hash code. 335 * 336 * @return A hash code. 337 */ 338 @Override 339 public int hashCode() { 340 int hash = super.hashCode(); // equals calls superclass, hashCode must also 341 hash = 67 * hash + (int) (Double.doubleToLongBits(this.x0) ^ 342 (Double.doubleToLongBits(this.x0) >>> 32)); 343 hash = 67 * hash + (int) (Double.doubleToLongBits(this.y0) ^ 344 (Double.doubleToLongBits(this.y0) >>> 32)); 345 hash = 67 * hash + (int) (Double.doubleToLongBits(this.x1) ^ 346 (Double.doubleToLongBits(this.x1) >>> 32)); 347 hash = 67 * hash + (int) (Double.doubleToLongBits(this.y1) ^ 348 (Double.doubleToLongBits(this.y1) >>> 32)); 349 hash = 67 * hash + Objects.hashCode(this.stroke); 350 hash = 67 * hash + HashUtils.hashCodeForPaint(this.outlinePaint); 351 hash = 67 * hash + HashUtils.hashCodeForPaint(this.fillPaint); 352 return hash; 353 } 354 355 /** 356 * Returns a clone. 357 * 358 * @return A clone. 359 * 360 * @throws CloneNotSupportedException not thrown by this class, but may be 361 * by subclasses. 362 */ 363 @Override 364 public Object clone() throws CloneNotSupportedException { 365 return super.clone(); 366 } 367 368 /** 369 * Provides serialization support. 370 * 371 * @param stream the output stream ({@code null} not permitted). 372 * 373 * @throws IOException if there is an I/O error. 374 */ 375 private void writeObject(ObjectOutputStream stream) throws IOException { 376 stream.defaultWriteObject(); 377 SerialUtils.writeStroke(this.stroke, stream); 378 SerialUtils.writePaint(this.outlinePaint, stream); 379 SerialUtils.writePaint(this.fillPaint, stream); 380 } 381 382 /** 383 * Provides serialization support. 384 * 385 * @param stream the input stream ({@code null} not permitted). 386 * 387 * @throws IOException if there is an I/O error. 388 * @throws ClassNotFoundException if there is a classpath problem. 389 */ 390 private void readObject(ObjectInputStream stream) 391 throws IOException, ClassNotFoundException { 392 393 stream.defaultReadObject(); 394 this.stroke = SerialUtils.readStroke(stream); 395 this.outlinePaint = SerialUtils.readPaint(stream); 396 this.fillPaint = SerialUtils.readPaint(stream); 397 } 398 399}