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 * CategoryLineAnnotation.java 029 * --------------------------- 030 * (C) Copyright 2005-present, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 033 * Contributor(s): Peter Kolb (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; 051 052import org.jfree.chart.HashUtils; 053import org.jfree.chart.axis.CategoryAnchor; 054import org.jfree.chart.axis.CategoryAxis; 055import org.jfree.chart.axis.ValueAxis; 056import org.jfree.chart.event.AnnotationChangeEvent; 057import org.jfree.chart.plot.CategoryPlot; 058import org.jfree.chart.plot.Plot; 059import org.jfree.chart.plot.PlotOrientation; 060import org.jfree.chart.ui.RectangleEdge; 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.data.category.CategoryDataset; 066 067/** 068 * A line annotation that can be placed on a {@link CategoryPlot}. 069 */ 070public class CategoryLineAnnotation extends AbstractAnnotation 071 implements CategoryAnnotation, Cloneable, PublicCloneable, 072 Serializable { 073 074 /** For serialization. */ 075 static final long serialVersionUID = 3477740483341587984L; 076 077 /** The category for the start of the line. */ 078 private Comparable category1; 079 080 /** The value for the start of the line. */ 081 private double value1; 082 083 /** The category for the end of the line. */ 084 private Comparable category2; 085 086 /** The value for the end of the line. */ 087 private double value2; 088 089 /** The line color. */ 090 private transient Paint paint = Color.BLACK; 091 092 /** The line stroke. */ 093 private transient Stroke stroke = new BasicStroke(1.0f); 094 095 /** 096 * Creates a new annotation that draws a line between (category1, value1) 097 * and (category2, value2). 098 * 099 * @param category1 the category ({@code null} not permitted). 100 * @param value1 the value (must be finite). 101 * @param category2 the category ({@code null} not permitted). 102 * @param value2 the value (must be finite). 103 * @param paint the line color ({@code null} not permitted). 104 * @param stroke the line stroke ({@code null} not permitted). 105 */ 106 public CategoryLineAnnotation(Comparable category1, double value1, 107 Comparable category2, double value2, 108 Paint paint, Stroke stroke) { 109 super(); 110 Args.nullNotPermitted(category1, "category1"); 111 Args.requireFinite(value1, "value1"); 112 Args.nullNotPermitted(category2, "category2"); 113 Args.requireFinite(value2, "value2"); 114 Args.nullNotPermitted(paint, "paint"); 115 Args.nullNotPermitted(stroke, "stroke"); 116 this.category1 = category1; 117 this.value1 = value1; 118 this.category2 = category2; 119 this.value2 = value2; 120 this.paint = paint; 121 this.stroke = stroke; 122 } 123 124 /** 125 * Returns the category for the start of the line. 126 * 127 * @return The category for the start of the line (never {@code null}). 128 * 129 * @see #setCategory1(Comparable) 130 */ 131 public Comparable getCategory1() { 132 return this.category1; 133 } 134 135 /** 136 * Sets the category for the start of the line and sends an 137 * {@link AnnotationChangeEvent} to all registered listeners. 138 * 139 * @param category the category ({@code null} not permitted). 140 * 141 * @see #getCategory1() 142 */ 143 public void setCategory1(Comparable category) { 144 Args.nullNotPermitted(category, "category"); 145 this.category1 = category; 146 fireAnnotationChanged(); 147 } 148 149 /** 150 * Returns the y-value for the start of the line. 151 * 152 * @return The y-value for the start of the line. 153 * 154 * @see #setValue1(double) 155 */ 156 public double getValue1() { 157 return this.value1; 158 } 159 160 /** 161 * Sets the y-value for the start of the line and sends an 162 * {@link AnnotationChangeEvent} to all registered listeners. 163 * 164 * @param value the value (must be finite). 165 * 166 * @see #getValue1() 167 */ 168 public void setValue1(double value) { 169 Args.requireFinite(value, "value"); 170 this.value1 = value; 171 fireAnnotationChanged(); 172 } 173 174 /** 175 * Returns the category for the end of the line. 176 * 177 * @return The category for the end of the line (never {@code null}). 178 * 179 * @see #setCategory2(Comparable) 180 */ 181 public Comparable getCategory2() { 182 return this.category2; 183 } 184 185 /** 186 * Sets the category for the end of the line and sends an 187 * {@link AnnotationChangeEvent} to all registered listeners. 188 * 189 * @param category the category ({@code null} not permitted). 190 * 191 * @see #getCategory2() 192 */ 193 public void setCategory2(Comparable category) { 194 Args.nullNotPermitted(category, "category"); 195 this.category2 = category; 196 fireAnnotationChanged(); 197 } 198 199 /** 200 * Returns the y-value for the end of the line. 201 * 202 * @return The y-value for the end of the line. 203 * 204 * @see #setValue2(double) 205 */ 206 public double getValue2() { 207 return this.value2; 208 } 209 210 /** 211 * Sets the y-value for the end of the line and sends an 212 * {@link AnnotationChangeEvent} to all registered listeners. 213 * 214 * @param value the value (must be finite). 215 * 216 * @see #getValue2() 217 */ 218 public void setValue2(double value) { 219 Args.requireFinite(value, "value"); 220 this.value2 = value; 221 fireAnnotationChanged(); 222 } 223 224 /** 225 * Returns the paint used to draw the connecting line. 226 * 227 * @return The paint (never {@code null}). 228 * 229 * @see #setPaint(Paint) 230 */ 231 public Paint getPaint() { 232 return this.paint; 233 } 234 235 /** 236 * Sets the paint used to draw the connecting line and sends an 237 * {@link AnnotationChangeEvent} to all registered listeners. 238 * 239 * @param paint the paint ({@code null} not permitted). 240 * 241 * @see #getPaint() 242 */ 243 public void setPaint(Paint paint) { 244 Args.nullNotPermitted(paint, "paint"); 245 this.paint = paint; 246 fireAnnotationChanged(); 247 } 248 249 /** 250 * Returns the stroke used to draw the connecting line. 251 * 252 * @return The stroke (never {@code null}). 253 * 254 * @see #setStroke(Stroke) 255 */ 256 public Stroke getStroke() { 257 return this.stroke; 258 } 259 260 /** 261 * Sets the stroke used to draw the connecting line and sends an 262 * {@link AnnotationChangeEvent} to all registered listeners. 263 * 264 * @param stroke the stroke ({@code null} not permitted). 265 * 266 * @see #getStroke() 267 */ 268 public void setStroke(Stroke stroke) { 269 Args.nullNotPermitted(stroke, "stroke"); 270 this.stroke = stroke; 271 fireAnnotationChanged(); 272 } 273 274 /** 275 * Draws the annotation. 276 * 277 * @param g2 the graphics device. 278 * @param plot the plot. 279 * @param dataArea the data area. 280 * @param domainAxis the domain axis. 281 * @param rangeAxis the range axis. 282 */ 283 @Override 284 public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea, 285 CategoryAxis domainAxis, ValueAxis rangeAxis) { 286 287 CategoryDataset dataset = plot.getDataset(); 288 int catIndex1 = dataset.getColumnIndex(this.category1); 289 int catIndex2 = dataset.getColumnIndex(this.category2); 290 int catCount = dataset.getColumnCount(); 291 292 double lineX1 = 0.0f; 293 double lineY1 = 0.0f; 294 double lineX2 = 0.0f; 295 double lineY2 = 0.0f; 296 PlotOrientation orientation = plot.getOrientation(); 297 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 298 plot.getDomainAxisLocation(), orientation); 299 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 300 plot.getRangeAxisLocation(), orientation); 301 302 if (orientation == PlotOrientation.HORIZONTAL) { 303 lineY1 = domainAxis.getCategoryJava2DCoordinate( 304 CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea, 305 domainEdge); 306 lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge); 307 lineY2 = domainAxis.getCategoryJava2DCoordinate( 308 CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea, 309 domainEdge); 310 lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge); 311 } else if (orientation == PlotOrientation.VERTICAL) { 312 lineX1 = domainAxis.getCategoryJava2DCoordinate( 313 CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea, 314 domainEdge); 315 lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge); 316 lineX2 = domainAxis.getCategoryJava2DCoordinate( 317 CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea, 318 domainEdge); 319 lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge); 320 } 321 g2.setPaint(this.paint); 322 g2.setStroke(this.stroke); 323 g2.drawLine((int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2); 324 } 325 326 /** 327 * Tests this object for equality with another. 328 * 329 * @param obj the object ({@code null} permitted). 330 * 331 * @return {@code true} or {@code false}. 332 */ 333 @Override 334 public boolean equals(Object obj) { 335 if (obj == this) { 336 return true; 337 } 338 if (!(obj instanceof CategoryLineAnnotation)) { 339 return false; 340 } 341 CategoryLineAnnotation that = (CategoryLineAnnotation) obj; 342 if (!Objects.equals(this.category1, that.category1)) { 343 return false; 344 } 345 if (Double.doubleToLongBits(this.value1) != 346 Double.doubleToLongBits(that.value1)) { 347 return false; 348 } 349 if (!Objects.equals(this.category2, that.category2)) { 350 return false; 351 } 352 if (Double.doubleToLongBits(this.value2) != 353 Double.doubleToLongBits(that.value2)) { 354 return false; 355 } 356 if (!PaintUtils.equal(this.paint, that.paint)) { 357 return false; 358 } 359 if (!Objects.equals(this.stroke, that.stroke)) { 360 return false; 361 } 362 // fix the "equals not symmetric" problem 363 if (!that.canEqual(this)) { 364 return false; 365 } 366 367 return super.equals(obj); 368 } 369 370 /** 371 * Ensures symmetry between super/subclass implementations of equals. For 372 * more detail, see http://jqno.nl/equalsverifier/manual/inheritance. 373 * 374 * @param other Object 375 * 376 * @return true ONLY if the parameter is THIS class type 377 */ 378 @Override 379 public boolean canEqual(Object other) { 380 // fix the "equals not symmetric" problem 381 return (other instanceof CategoryLineAnnotation); 382 } 383 384 /** 385 * Returns a hash code for this instance. 386 * 387 * @return A hash code. 388 */ 389 @Override 390 public int hashCode() { 391 int result = super.hashCode(); // equals calls superclass, hashCode must also 392 result = 37 * result + Objects.hashCode(this.category1); 393 long temp = Double.doubleToLongBits(this.value1); 394 result = 37 * result + (int) (temp ^ (temp >>> 32)); 395 result = 37 * result + Objects.hashCode(this.category2); 396 temp = Double.doubleToLongBits(this.value2); 397 result = 37 * result + (int) (temp ^ (temp >>> 32)); 398 result = 37 * result + HashUtils.hashCodeForPaint(this.paint); 399 result = 37 * result + Objects.hashCode(this.stroke); 400 return result; 401 } 402 403 /** 404 * Returns a clone of the annotation. 405 * 406 * @return A clone. 407 * 408 * @throws CloneNotSupportedException this class will not throw this 409 * exception, but subclasses (if any) might. 410 */ 411 @Override 412 public Object clone() throws CloneNotSupportedException { 413 return super.clone(); 414 } 415 416 /** 417 * Provides serialization support. 418 * 419 * @param stream the output stream. 420 * 421 * @throws IOException if there is an I/O error. 422 */ 423 private void writeObject(ObjectOutputStream stream) throws IOException { 424 stream.defaultWriteObject(); 425 SerialUtils.writePaint(this.paint, stream); 426 SerialUtils.writeStroke(this.stroke, stream); 427 } 428 429 /** 430 * Provides serialization support. 431 * 432 * @param stream the input stream. 433 * 434 * @throws IOException if there is an I/O error. 435 * @throws ClassNotFoundException if there is a classpath problem. 436 */ 437 private void readObject(ObjectInputStream stream) 438 throws IOException, ClassNotFoundException { 439 stream.defaultReadObject(); 440 this.paint = SerialUtils.readPaint(stream); 441 this.stroke = SerialUtils.readStroke(stream); 442 } 443 444}