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