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 * XYLineAnnotation.java 029 * --------------------- 030 * (C) Copyright 2003-present, by David Gilbert. 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.Line2D; 046import java.awt.geom.Rectangle2D; 047import java.io.IOException; 048import java.io.ObjectInputStream; 049import java.io.ObjectOutputStream; 050import java.io.Serializable; 051import java.util.Objects; 052import org.jfree.chart.HashUtils; 053 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.LineUtils; 061import org.jfree.chart.util.PaintUtils; 062import org.jfree.chart.util.Args; 063import org.jfree.chart.util.PublicCloneable; 064import org.jfree.chart.util.SerialUtils; 065import org.jfree.chart.util.ShapeUtils; 066 067/** 068 * A simple line annotation that can be placed on an {@link XYPlot}. 069 * Instances of this class are immutable. 070 */ 071public class XYLineAnnotation extends AbstractXYAnnotation 072 implements Cloneable, PublicCloneable, Serializable { 073 074 /** For serialization. */ 075 private static final long serialVersionUID = -80535465244091334L; 076 077 /** The x-coordinate. */ 078 private double x1; 079 080 /** The y-coordinate. */ 081 private double y1; 082 083 /** The x-coordinate. */ 084 private double x2; 085 086 /** The y-coordinate. */ 087 private double y2; 088 089 /** The line stroke. */ 090 private transient Stroke stroke; 091 092 /** The line color. */ 093 private transient Paint paint; 094 095 /** 096 * Creates a new annotation that draws a line from (x1, y1) to (x2, y2) 097 * where the coordinates are measured in data space (that is, against the 098 * plot's axes). All the line coordinates are required to be finite values. 099 * 100 * @param x1 the x-coordinate for the start of the line. 101 * @param y1 the y-coordinate for the start of the line. 102 * @param x2 the x-coordinate for the end of the line. 103 * @param y2 the y-coordinate for the end of the line. 104 */ 105 public XYLineAnnotation(double x1, double y1, double x2, double y2) { 106 this(x1, y1, x2, y2, new BasicStroke(1.0f), Color.BLACK); 107 } 108 109 /** 110 * Creates a new annotation that draws a line from (x1, y1) to (x2, y2) 111 * where the coordinates are measured in data space (that is, against the 112 * plot's axes). 113 * 114 * @param x1 the x-coordinate for the start of the line (must be finite). 115 * @param y1 the y-coordinate for the start of the line (must be finite). 116 * @param x2 the x-coordinate for the end of the line (must be finite). 117 * @param y2 the y-coordinate for the end of the line (must be finite). 118 * @param stroke the line stroke ({@code null} not permitted). 119 * @param paint the line color ({@code null} not permitted). 120 */ 121 public XYLineAnnotation(double x1, double y1, double x2, double y2, 122 Stroke stroke, Paint paint) { 123 super(); 124 Args.nullNotPermitted(stroke, "stroke"); 125 Args.nullNotPermitted(paint, "paint"); 126 Args.requireFinite(x1, "x1"); 127 Args.requireFinite(y1, "y1"); 128 Args.requireFinite(x2, "x2"); 129 Args.requireFinite(y2, "y2"); 130 this.x1 = x1; 131 this.y1 = y1; 132 this.x2 = x2; 133 this.y2 = y2; 134 this.stroke = stroke; 135 this.paint = paint; 136 } 137 138 /** 139 * Returns the x-coordinate for the starting point of the line (set in the 140 * constructor). 141 * 142 * @return The x-coordinate for the starting point of the line. 143 */ 144 public double getX1() { 145 return x1; 146 } 147 148 /** 149 * Returns the y-coordinate for the starting point of the line (set in the 150 * constructor). 151 * 152 * @return The y-coordinate for the starting point of the line. 153 */ 154 public double getY1() { 155 return y1; 156 } 157 158 /** 159 * Returns the x-coordinate for the ending point of the line (set in the 160 * constructor). 161 * 162 * @return The x-coordinate for the ending point of the line. 163 */ 164 public double getX2() { 165 return x2; 166 } 167 168 /** 169 * Returns the y-coordinate for the ending point of the line (set in the 170 * constructor). 171 * 172 * @return The y-coordinate for the ending point of the line. 173 */ 174 public double getY2() { 175 return y2; 176 } 177 178 /** 179 * Returns the stroke used for drawing the line (set in the constructor). 180 * 181 * @return The stroke (never {@code null}). 182 */ 183 public Stroke getStroke() { 184 return stroke; 185 } 186 187 /** 188 * Returns the paint used for drawing the line (set in the constructor). 189 * 190 * @return The paint (never {@code null}). 191 */ 192 public Paint getPaint() { 193 return paint; 194 } 195 196 /** 197 * Draws the annotation. This method is called by the {@link XYPlot} 198 * class, you won't normally need to call it yourself. 199 * 200 * @param g2 the graphics device. 201 * @param plot the plot. 202 * @param dataArea the data area. 203 * @param domainAxis the domain axis. 204 * @param rangeAxis the range axis. 205 * @param rendererIndex the renderer index. 206 * @param info if supplied, this info object will be populated with 207 * entity information. 208 */ 209 @Override 210 public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, 211 ValueAxis domainAxis, ValueAxis rangeAxis, 212 int rendererIndex, 213 PlotRenderingInfo info) { 214 215 PlotOrientation orientation = plot.getOrientation(); 216 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 217 plot.getDomainAxisLocation(), orientation); 218 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 219 plot.getRangeAxisLocation(), orientation); 220 float j2DX1 = 0.0f; 221 float j2DX2 = 0.0f; 222 float j2DY1 = 0.0f; 223 float j2DY2 = 0.0f; 224 if (orientation == PlotOrientation.VERTICAL) { 225 j2DX1 = (float) domainAxis.valueToJava2D(this.x1, dataArea, 226 domainEdge); 227 j2DY1 = (float) rangeAxis.valueToJava2D(this.y1, dataArea, 228 rangeEdge); 229 j2DX2 = (float) domainAxis.valueToJava2D(this.x2, dataArea, 230 domainEdge); 231 j2DY2 = (float) rangeAxis.valueToJava2D(this.y2, dataArea, 232 rangeEdge); 233 } else if (orientation == PlotOrientation.HORIZONTAL) { 234 j2DY1 = (float) domainAxis.valueToJava2D(this.x1, dataArea, 235 domainEdge); 236 j2DX1 = (float) rangeAxis.valueToJava2D(this.y1, dataArea, 237 rangeEdge); 238 j2DY2 = (float) domainAxis.valueToJava2D(this.x2, dataArea, 239 domainEdge); 240 j2DX2 = (float) rangeAxis.valueToJava2D(this.y2, dataArea, 241 rangeEdge); 242 } 243 g2.setPaint(this.paint); 244 g2.setStroke(this.stroke); 245 Line2D line = new Line2D.Float(j2DX1, j2DY1, j2DX2, j2DY2); 246 // line is clipped to avoid JRE bug 6574155, for more info 247 // see JFreeChart bug 2221495 248 boolean visible = LineUtils.clipLine(line, dataArea); 249 if (visible) { 250 g2.draw(line); 251 } 252 253 String toolTip = getToolTipText(); 254 String url = getURL(); 255 if (toolTip != null || url != null) { 256 addEntity(info, ShapeUtils.createLineRegion(line, 1.0f), 257 rendererIndex, toolTip, url); 258 } 259 } 260 261 /** 262 * Tests this object for equality with an arbitrary object. 263 * 264 * @param obj the object to test against ({@code null} permitted). 265 * 266 * @return {@code true} or {@code false}. 267 */ 268 @Override 269 public boolean equals(Object obj) { 270 if (obj == this) { 271 return true; 272 } 273 if (!(obj instanceof XYLineAnnotation)) { 274 return false; 275 } 276 XYLineAnnotation that = (XYLineAnnotation) obj; 277 if (Double.doubleToLongBits(this.x1) != Double.doubleToLongBits(that.x1)) { 278 return false; 279 } 280 if (Double.doubleToLongBits(this.y1) != Double.doubleToLongBits(that.y1)) { 281 return false; 282 } 283 if (Double.doubleToLongBits(this.x2) != Double.doubleToLongBits(that.x2)) { 284 return false; 285 } 286 if (Double.doubleToLongBits(this.y2) != Double.doubleToLongBits(that.y2)) { 287 return false; 288 } 289 if (!PaintUtils.equal(this.paint, that.paint)) { 290 return false; 291 } 292 if (!Objects.equals(this.stroke, that.stroke)) { 293 return false; 294 } 295 // fix the "equals not symmetric" problem 296 if (!that.canEqual(this)) { 297 return false; 298 } 299 300 return super.equals(obj); 301 } 302 303 /** 304 * Ensures symmetry between super/subclass implementations of equals. For 305 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 306 * 307 * @param other Object 308 * 309 * @return true ONLY if the parameter is THIS class type 310 */ 311 @Override 312 public boolean canEqual(Object other) { 313 // fix the "equals not symmetric" problem 314 return (other instanceof XYLineAnnotation); 315 } 316 317 /** 318 * Returns a hash code. 319 * 320 * @return A hash code. 321 */ 322 @Override 323 public int hashCode() { 324 int hash = super.hashCode(); // equals calls superclass, hashCode must also 325 hash = 89 * hash + (int) (Double.doubleToLongBits(this.x1) ^ 326 (Double.doubleToLongBits(this.x1) >>> 32)); 327 hash = 89 * hash + (int) (Double.doubleToLongBits(this.y1) ^ 328 (Double.doubleToLongBits(this.y1) >>> 32)); 329 hash = 89 * hash + (int) (Double.doubleToLongBits(this.x2) ^ 330 (Double.doubleToLongBits(this.x2) >>> 32)); 331 hash = 89 * hash + (int) (Double.doubleToLongBits(this.y2) ^ 332 (Double.doubleToLongBits(this.y2) >>> 32)); 333 hash = 89 * hash + Objects.hashCode(this.stroke); 334 hash = 89 * hash + HashUtils.hashCodeForPaint(this.paint); 335 return hash; 336 } 337 338 /** 339 * Returns a clone of the annotation. 340 * 341 * @return A clone. 342 * 343 * @throws CloneNotSupportedException if the annotation can't be cloned. 344 */ 345 @Override 346 public Object clone() throws CloneNotSupportedException { 347 return super.clone(); 348 } 349 350 /** 351 * Provides serialization support. 352 * 353 * @param stream the output stream. 354 * 355 * @throws IOException if there is an I/O error. 356 */ 357 private void writeObject(ObjectOutputStream stream) throws IOException { 358 stream.defaultWriteObject(); 359 SerialUtils.writePaint(this.paint, stream); 360 SerialUtils.writeStroke(this.stroke, stream); 361 } 362 363 /** 364 * Provides serialization support. 365 * 366 * @param stream the input stream. 367 * 368 * @throws IOException if there is an I/O error. 369 * @throws ClassNotFoundException if there is a classpath problem. 370 */ 371 private void readObject(ObjectInputStream stream) 372 throws IOException, ClassNotFoundException { 373 stream.defaultReadObject(); 374 this.paint = SerialUtils.readPaint(stream); 375 this.stroke = SerialUtils.readStroke(stream); 376 } 377 378}