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