001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2022, 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-2022, by David Gilbert. 031 * 032 * Original Author: David Gilbert; 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.internal.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.api.RectangleEdge; 060import org.jfree.chart.internal.PaintUtils; 061import org.jfree.chart.internal.Args; 062import org.jfree.chart.api.PublicCloneable; 063import org.jfree.chart.internal.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 (must be finite). 100 * @param category2 the category ({@code null} not permitted). 101 * @param value2 the value (must be finite). 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 // FIXME : the order of the paint and stroke parameters is reversed 109 // compared to XYLineAnnotation...should be consistent 110 super(); 111 Args.nullNotPermitted(category1, "category1"); 112 Args.requireFinite(value1, "value1"); 113 Args.nullNotPermitted(category2, "category2"); 114 Args.requireFinite(value2, "value2"); 115 Args.nullNotPermitted(paint, "paint"); 116 Args.nullNotPermitted(stroke, "stroke"); 117 this.category1 = category1; 118 this.value1 = value1; 119 this.category2 = category2; 120 this.value2 = value2; 121 this.paint = paint; 122 this.stroke = stroke; 123 } 124 125 /** 126 * Returns the category for the start of the line. 127 * 128 * @return The category for the start of the line (never {@code null}). 129 * 130 * @see #setCategory1(Comparable) 131 */ 132 public Comparable getCategory1() { 133 return this.category1; 134 } 135 136 /** 137 * Sets the category for the start of the line and sends an 138 * {@link AnnotationChangeEvent} to all registered listeners. 139 * 140 * @param category the category ({@code null} not permitted). 141 * 142 * @see #getCategory1() 143 */ 144 public void setCategory1(Comparable category) { 145 Args.nullNotPermitted(category, "category"); 146 this.category1 = category; 147 fireAnnotationChanged(); 148 } 149 150 /** 151 * Returns the y-value for the start of the line. 152 * 153 * @return The y-value for the start of the line. 154 * 155 * @see #setValue1(double) 156 */ 157 public double getValue1() { 158 return this.value1; 159 } 160 161 /** 162 * Sets the y-value for the start of the line and sends an 163 * {@link AnnotationChangeEvent} to all registered listeners. 164 * 165 * @param value the value (must be finite). 166 * 167 * @see #getValue1() 168 */ 169 public void setValue1(double value) { 170 Args.requireFinite(value, "value"); 171 this.value1 = value; 172 fireAnnotationChanged(); 173 } 174 175 /** 176 * Returns the category for the end of the line. 177 * 178 * @return The category for the end of the line (never {@code null}). 179 * 180 * @see #setCategory2(Comparable) 181 */ 182 public Comparable getCategory2() { 183 return this.category2; 184 } 185 186 /** 187 * Sets the category for the end of the line and sends an 188 * {@link AnnotationChangeEvent} to all registered listeners. 189 * 190 * @param category the category ({@code null} not permitted). 191 * 192 * @see #getCategory2() 193 */ 194 public void setCategory2(Comparable category) { 195 Args.nullNotPermitted(category, "category"); 196 this.category2 = category; 197 fireAnnotationChanged(); 198 } 199 200 /** 201 * Returns the y-value for the end of the line. 202 * 203 * @return The y-value for the end of the line. 204 * 205 * @see #setValue2(double) 206 */ 207 public double getValue2() { 208 return this.value2; 209 } 210 211 /** 212 * Sets the y-value for the end of the line and sends an 213 * {@link AnnotationChangeEvent} to all registered listeners. 214 * 215 * @param value the value (must be finite). 216 * 217 * @see #getValue2() 218 */ 219 public void setValue2(double value) { 220 Args.requireFinite(value, "value"); 221 this.value2 = value; 222 fireAnnotationChanged(); 223 } 224 225 /** 226 * Returns the paint used to draw the connecting line. 227 * 228 * @return The paint (never {@code null}). 229 * 230 * @see #setPaint(Paint) 231 */ 232 public Paint getPaint() { 233 return this.paint; 234 } 235 236 /** 237 * Sets the paint used to draw the connecting line and sends an 238 * {@link AnnotationChangeEvent} to all registered listeners. 239 * 240 * @param paint the paint ({@code null} not permitted). 241 * 242 * @see #getPaint() 243 */ 244 public void setPaint(Paint paint) { 245 Args.nullNotPermitted(paint, "paint"); 246 this.paint = paint; 247 fireAnnotationChanged(); 248 } 249 250 /** 251 * Returns the stroke used to draw the connecting line. 252 * 253 * @return The stroke (never {@code null}). 254 * 255 * @see #setStroke(Stroke) 256 */ 257 public Stroke getStroke() { 258 return this.stroke; 259 } 260 261 /** 262 * Sets the stroke used to draw the connecting line and sends an 263 * {@link AnnotationChangeEvent} to all registered listeners. 264 * 265 * @param stroke the stroke ({@code null} not permitted). 266 * 267 * @see #getStroke() 268 */ 269 public void setStroke(Stroke stroke) { 270 Args.nullNotPermitted(stroke, "stroke"); 271 this.stroke = stroke; 272 fireAnnotationChanged(); 273 } 274 275 /** 276 * Draws the annotation. 277 * 278 * @param g2 the graphics device. 279 * @param plot the plot. 280 * @param dataArea the data area. 281 * @param domainAxis the domain axis. 282 * @param rangeAxis the range axis. 283 */ 284 @Override 285 public void draw(Graphics2D g2, CategoryPlot plot, Rectangle2D dataArea, 286 CategoryAxis domainAxis, ValueAxis rangeAxis) { 287 288 CategoryDataset dataset = plot.getDataset(); 289 int catIndex1 = dataset.getColumnIndex(this.category1); 290 int catIndex2 = dataset.getColumnIndex(this.category2); 291 int catCount = dataset.getColumnCount(); 292 293 double lineX1 = 0.0f; 294 double lineY1 = 0.0f; 295 double lineX2 = 0.0f; 296 double lineY2 = 0.0f; 297 PlotOrientation orientation = plot.getOrientation(); 298 RectangleEdge domainEdge = Plot.resolveDomainAxisLocation( 299 plot.getDomainAxisLocation(), orientation); 300 RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation( 301 plot.getRangeAxisLocation(), orientation); 302 303 if (orientation == PlotOrientation.HORIZONTAL) { 304 lineY1 = domainAxis.getCategoryJava2DCoordinate( 305 CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea, 306 domainEdge); 307 lineX1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge); 308 lineY2 = domainAxis.getCategoryJava2DCoordinate( 309 CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea, 310 domainEdge); 311 lineX2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge); 312 } else if (orientation == PlotOrientation.VERTICAL) { 313 lineX1 = domainAxis.getCategoryJava2DCoordinate( 314 CategoryAnchor.MIDDLE, catIndex1, catCount, dataArea, 315 domainEdge); 316 lineY1 = rangeAxis.valueToJava2D(this.value1, dataArea, rangeEdge); 317 lineX2 = domainAxis.getCategoryJava2DCoordinate( 318 CategoryAnchor.MIDDLE, catIndex2, catCount, dataArea, 319 domainEdge); 320 lineY2 = rangeAxis.valueToJava2D(this.value2, dataArea, rangeEdge); 321 } 322 g2.setPaint(this.paint); 323 g2.setStroke(this.stroke); 324 g2.drawLine((int) lineX1, (int) lineY1, (int) lineX2, (int) lineY2); 325 } 326 327 /** 328 * Tests this object for equality with another. 329 * 330 * @param obj the object ({@code null} permitted). 331 * 332 * @return {@code true} or {@code false}. 333 */ 334 @Override 335 public boolean equals(Object obj) { 336 if (obj == this) { 337 return true; 338 } 339 if (!(obj instanceof CategoryLineAnnotation)) { 340 return false; 341 } 342 CategoryLineAnnotation that = (CategoryLineAnnotation) obj; 343 if (!this.category1.equals(that.getCategory1())) { 344 return false; 345 } 346 if (this.value1 != that.getValue1()) { 347 return false; 348 } 349 if (!this.category2.equals(that.getCategory2())) { 350 return false; 351 } 352 if (this.value2 != that.getValue2()) { 353 return false; 354 } 355 if (!PaintUtils.equal(this.paint, that.paint)) { 356 return false; 357 } 358 if (!Objects.equals(this.stroke, that.stroke)) { 359 return false; 360 } 361 return true; 362 } 363 364 /** 365 * Returns a hash code for this instance. 366 * 367 * @return A hash code. 368 */ 369 @Override 370 public int hashCode() { 371 int result = 193; 372 result = 37 * result + this.category1.hashCode(); 373 long temp = Double.doubleToLongBits(this.value1); 374 result = 37 * result + (int) (temp ^ (temp >>> 32)); 375 result = 37 * result + this.category2.hashCode(); 376 temp = Double.doubleToLongBits(this.value2); 377 result = 37 * result + (int) (temp ^ (temp >>> 32)); 378 result = 37 * result + HashUtils.hashCodeForPaint(this.paint); 379 result = 37 * result + this.stroke.hashCode(); 380 return result; 381 } 382 383 /** 384 * Returns a clone of the annotation. 385 * 386 * @return A clone. 387 * 388 * @throws CloneNotSupportedException this class will not throw this 389 * exception, but subclasses (if any) might. 390 */ 391 @Override 392 public Object clone() throws CloneNotSupportedException { 393 return super.clone(); 394 } 395 396 /** 397 * Provides serialization support. 398 * 399 * @param stream the output stream. 400 * 401 * @throws IOException if there is an I/O error. 402 */ 403 private void writeObject(ObjectOutputStream stream) throws IOException { 404 stream.defaultWriteObject(); 405 SerialUtils.writePaint(this.paint, stream); 406 SerialUtils.writeStroke(this.stroke, stream); 407 } 408 409 /** 410 * Provides serialization support. 411 * 412 * @param stream the input stream. 413 * 414 * @throws IOException if there is an I/O error. 415 * @throws ClassNotFoundException if there is a classpath problem. 416 */ 417 private void readObject(ObjectInputStream stream) 418 throws IOException, ClassNotFoundException { 419 stream.defaultReadObject(); 420 this.paint = SerialUtils.readPaint(stream); 421 this.stroke = SerialUtils.readStroke(stream); 422 } 423 424}