001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-2007, 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 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 025 * in the United States and other countries.] 026 * 027 * ---------------- 028 * DialPointer.java 029 * ---------------- 030 * (C) Copyright 2006-2007, by Object Refinery Limited. 031 * 032 * Original Author: David Gilbert (for Object Refinery Limited); 033 * Contributor(s): -; 034 * 035 * Changes 036 * ------- 037 * 03-Nov-2006 : Version 1 (DG); 038 * 17-Oct-2007 : Added equals() overrides (DG); 039 * 24-Oct-2007 : Implemented PublicCloneable, changed default radius, 040 * and added argument checks (DG); 041 * 042 */ 043 044package org.jfree.chart.plot.dial; 045 046import java.awt.BasicStroke; 047import java.awt.Color; 048import java.awt.Graphics2D; 049import java.awt.Paint; 050import java.awt.Stroke; 051import java.awt.geom.Arc2D; 052import java.awt.geom.GeneralPath; 053import java.awt.geom.Line2D; 054import java.awt.geom.Point2D; 055import java.awt.geom.Rectangle2D; 056import java.io.IOException; 057import java.io.ObjectInputStream; 058import java.io.ObjectOutputStream; 059import java.io.Serializable; 060 061import org.jfree.chart.HashUtilities; 062import org.jfree.io.SerialUtilities; 063import org.jfree.util.PaintUtilities; 064import org.jfree.util.PublicCloneable; 065 066/** 067 * A base class for the pointer in a {@link DialPlot}. 068 */ 069public abstract class DialPointer extends AbstractDialLayer 070 implements DialLayer, Cloneable, PublicCloneable, Serializable { 071 072 /** The needle radius. */ 073 double radius; 074 075 /** 076 * The dataset index for the needle. 077 */ 078 int datasetIndex; 079 080 /** 081 * Creates a new <code>DialPointer</code> instance. 082 */ 083 protected DialPointer() { 084 this(0); 085 } 086 087 /** 088 * Creates a new pointer for the specified dataset. 089 * 090 * @param datasetIndex the dataset index. 091 */ 092 protected DialPointer(int datasetIndex) { 093 this.radius = 0.9; 094 this.datasetIndex = datasetIndex; 095 } 096 097 /** 098 * Returns the dataset index that the pointer maps to. 099 * 100 * @return The dataset index. 101 * 102 * @see #getDatasetIndex() 103 */ 104 public int getDatasetIndex() { 105 return this.datasetIndex; 106 } 107 108 /** 109 * Sets the dataset index for the pointer and sends a 110 * {@link DialLayerChangeEvent} to all registered listeners. 111 * 112 * @param index the index. 113 * 114 * @see #getDatasetIndex() 115 */ 116 public void setDatasetIndex(int index) { 117 this.datasetIndex = index; 118 notifyListeners(new DialLayerChangeEvent(this)); 119 } 120 121 /** 122 * Returns the radius of the pointer, as a percentage of the dial's 123 * framing rectangle. 124 * 125 * @return The radius. 126 * 127 * @see #setRadius(double) 128 */ 129 public double getRadius() { 130 return this.radius; 131 } 132 133 /** 134 * Sets the radius of the pointer and sends a 135 * {@link DialLayerChangeEvent} to all registered listeners. 136 * 137 * @param radius the radius. 138 * 139 * @see #getRadius() 140 */ 141 public void setRadius(double radius) { 142 this.radius = radius; 143 notifyListeners(new DialLayerChangeEvent(this)); 144 } 145 146 /** 147 * Returns <code>true</code> to indicate that this layer should be 148 * clipped within the dial window. 149 * 150 * @return <code>true</code>. 151 */ 152 public boolean isClippedToWindow() { 153 return true; 154 } 155 156 /** 157 * Checks this instance for equality with an arbitrary object. 158 * 159 * @param obj the object (<code>null</code> not permitted). 160 * 161 * @return A boolean. 162 */ 163 public boolean equals(Object obj) { 164 if (obj == this) { 165 return true; 166 } 167 if (!(obj instanceof DialPointer)) { 168 return false; 169 } 170 DialPointer that = (DialPointer) obj; 171 if (this.datasetIndex != that.datasetIndex) { 172 return false; 173 } 174 if (this.radius != that.radius) { 175 return false; 176 } 177 return super.equals(obj); 178 } 179 180 /** 181 * Returns a hash code. 182 * 183 * @return A hash code. 184 */ 185 public int hashCode() { 186 int result = 23; 187 result = HashUtilities.hashCode(result, this.radius); 188 return result; 189 } 190 191 /** 192 * Returns a clone of the pointer. 193 * 194 * @return a clone. 195 * 196 * @throws CloneNotSupportedException if one of the attributes cannot 197 * be cloned. 198 */ 199 public Object clone() throws CloneNotSupportedException { 200 return super.clone(); 201 } 202 203 /** 204 * A dial pointer that draws a thin line (like a pin). 205 */ 206 public static class Pin extends DialPointer { 207 208 /** For serialization. */ 209 static final long serialVersionUID = -8445860485367689750L; 210 211 /** The paint. */ 212 private transient Paint paint; 213 214 /** The stroke. */ 215 private transient Stroke stroke; 216 217 /** 218 * Creates a new instance. 219 */ 220 public Pin() { 221 this(0); 222 } 223 224 /** 225 * Creates a new instance. 226 * 227 * @param datasetIndex the dataset index. 228 */ 229 public Pin(int datasetIndex) { 230 super(datasetIndex); 231 this.paint = Color.red; 232 this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, 233 BasicStroke.JOIN_BEVEL); 234 } 235 236 /** 237 * Returns the paint. 238 * 239 * @return The paint (never <code>null</code>). 240 * 241 * @see #setPaint(Paint) 242 */ 243 public Paint getPaint() { 244 return this.paint; 245 } 246 247 /** 248 * Sets the paint and sends a {@link DialLayerChangeEvent} to all 249 * registered listeners. 250 * 251 * @param paint the paint (<code>null</code> not permitted). 252 * 253 * @see #getPaint() 254 */ 255 public void setPaint(Paint paint) { 256 if (paint == null) { 257 throw new IllegalArgumentException("Null 'paint' argument."); 258 } 259 this.paint = paint; 260 notifyListeners(new DialLayerChangeEvent(this)); 261 } 262 263 /** 264 * Returns the stroke. 265 * 266 * @return The stroke (never <code>null</code>). 267 * 268 * @see #setStroke(Stroke) 269 */ 270 public Stroke getStroke() { 271 return this.stroke; 272 } 273 274 /** 275 * Sets the stroke and sends a {@link DialLayerChangeEvent} to all 276 * registered listeners. 277 * 278 * @param stroke the stroke (<code>null</code> not permitted). 279 * 280 * @see #getStroke() 281 */ 282 public void setStroke(Stroke stroke) { 283 if (stroke == null) { 284 throw new IllegalArgumentException("Null 'stroke' argument."); 285 } 286 this.stroke = stroke; 287 notifyListeners(new DialLayerChangeEvent(this)); 288 } 289 290 /** 291 * Draws the pointer. 292 * 293 * @param g2 the graphics target. 294 * @param plot the plot. 295 * @param frame the dial's reference frame. 296 * @param view the dial's view. 297 */ 298 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 299 Rectangle2D view) { 300 301 g2.setPaint(this.paint); 302 g2.setStroke(this.stroke); 303 Rectangle2D arcRect = DialPlot.rectangleByRadius(frame, 304 this.radius, this.radius); 305 306 double value = plot.getValue(this.datasetIndex); 307 DialScale scale = plot.getScaleForDataset(this.datasetIndex); 308 double angle = scale.valueToAngle(value); 309 310 Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN); 311 Point2D pt = arc.getEndPoint(); 312 313 Line2D line = new Line2D.Double(frame.getCenterX(), 314 frame.getCenterY(), pt.getX(), pt.getY()); 315 g2.draw(line); 316 } 317 318 /** 319 * Tests this pointer for equality with an arbitrary object. 320 * 321 * @param obj the object (<code>null</code> permitted). 322 * 323 * @return A boolean. 324 */ 325 public boolean equals(Object obj) { 326 if (obj == this) { 327 return true; 328 } 329 if (!(obj instanceof DialPointer.Pin)) { 330 return false; 331 } 332 DialPointer.Pin that = (DialPointer.Pin) obj; 333 if (!PaintUtilities.equal(this.paint, that.paint)) { 334 return false; 335 } 336 if (!this.stroke.equals(that.stroke)) { 337 return false; 338 } 339 return super.equals(obj); 340 } 341 342 /** 343 * Returns a hash code for this instance. 344 * 345 * @return A hash code. 346 */ 347 public int hashCode() { 348 int result = super.hashCode(); 349 result = HashUtilities.hashCode(result, this.paint); 350 result = HashUtilities.hashCode(result, this.stroke); 351 return result; 352 } 353 354 /** 355 * Provides serialization support. 356 * 357 * @param stream the output stream. 358 * 359 * @throws IOException if there is an I/O error. 360 */ 361 private void writeObject(ObjectOutputStream stream) throws IOException { 362 stream.defaultWriteObject(); 363 SerialUtilities.writePaint(this.paint, stream); 364 SerialUtilities.writeStroke(this.stroke, stream); 365 } 366 367 /** 368 * Provides serialization support. 369 * 370 * @param stream the input stream. 371 * 372 * @throws IOException if there is an I/O error. 373 * @throws ClassNotFoundException if there is a classpath problem. 374 */ 375 private void readObject(ObjectInputStream stream) 376 throws IOException, ClassNotFoundException { 377 stream.defaultReadObject(); 378 this.paint = SerialUtilities.readPaint(stream); 379 this.stroke = SerialUtilities.readStroke(stream); 380 } 381 382 } 383 384 /** 385 * A dial pointer. 386 */ 387 public static class Pointer extends DialPointer { 388 389 /** For serialization. */ 390 static final long serialVersionUID = -4180500011963176960L; 391 392 /** 393 * The radius that defines the width of the pointer at the base. 394 */ 395 private double widthRadius; 396 397 /** 398 * Creates a new instance. 399 */ 400 public Pointer() { 401 this(0); 402 } 403 404 /** 405 * Creates a new instance. 406 * 407 * @param datasetIndex the dataset index. 408 */ 409 public Pointer(int datasetIndex) { 410 super(datasetIndex); 411 this.widthRadius = 0.05; 412 } 413 414 /** 415 * Returns the width radius. 416 * 417 * @return The width radius. 418 * 419 * @see #setWidthRadius(double) 420 */ 421 public double getWidthRadius() { 422 return this.widthRadius; 423 } 424 425 /** 426 * Sets the width radius and sends a {@link DialLayerChangeEvent} to 427 * all registered listeners. 428 * 429 * @param radius the radius 430 * 431 * @see #getWidthRadius() 432 */ 433 public void setWidthRadius(double radius) { 434 this.widthRadius = radius; 435 notifyListeners(new DialLayerChangeEvent(this)); 436 } 437 438 /** 439 * Draws the pointer. 440 * 441 * @param g2 the graphics target. 442 * @param plot the plot. 443 * @param frame the dial's reference frame. 444 * @param view the dial's view. 445 */ 446 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 447 Rectangle2D view) { 448 449 g2.setPaint(Color.blue); 450 g2.setStroke(new BasicStroke(1.0f)); 451 Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame, 452 this.radius, this.radius); 453 Rectangle2D widthRect = DialPlot.rectangleByRadius(frame, 454 this.widthRadius, this.widthRadius); 455 double value = plot.getValue(this.datasetIndex); 456 DialScale scale = plot.getScaleForDataset(this.datasetIndex); 457 double angle = scale.valueToAngle(value); 458 459 Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN); 460 Point2D pt1 = arc1.getEndPoint(); 461 Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0, 462 Arc2D.OPEN); 463 Point2D pt2 = arc2.getStartPoint(); 464 Point2D pt3 = arc2.getEndPoint(); 465 Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0, 466 Arc2D.OPEN); 467 Point2D pt4 = arc3.getStartPoint(); 468 469 GeneralPath gp = new GeneralPath(); 470 gp.moveTo((float) pt1.getX(), (float) pt1.getY()); 471 gp.lineTo((float) pt2.getX(), (float) pt2.getY()); 472 gp.lineTo((float) pt4.getX(), (float) pt4.getY()); 473 gp.lineTo((float) pt3.getX(), (float) pt3.getY()); 474 gp.closePath(); 475 g2.setPaint(Color.gray); 476 g2.fill(gp); 477 478 g2.setPaint(Color.black); 479 Line2D line = new Line2D.Double(frame.getCenterX(), 480 frame.getCenterY(), pt1.getX(), pt1.getY()); 481 g2.draw(line); 482 483 line.setLine(pt2, pt3); 484 g2.draw(line); 485 486 line.setLine(pt3, pt1); 487 g2.draw(line); 488 489 line.setLine(pt2, pt1); 490 g2.draw(line); 491 492 line.setLine(pt2, pt4); 493 g2.draw(line); 494 495 line.setLine(pt3, pt4); 496 g2.draw(line); 497 } 498 499 /** 500 * Tests this pointer for equality with an arbitrary object. 501 * 502 * @param obj the object (<code>null</code> permitted). 503 * 504 * @return A boolean. 505 */ 506 public boolean equals(Object obj) { 507 if (obj == this) { 508 return true; 509 } 510 if (!(obj instanceof DialPointer.Pointer)) { 511 return false; 512 } 513 DialPointer.Pointer that = (DialPointer.Pointer) obj; 514 515 if (this.widthRadius != that.widthRadius) { 516 return false; 517 } 518 return super.equals(obj); 519 } 520 521 /** 522 * Returns a hash code for this instance. 523 * 524 * @return A hash code. 525 */ 526 public int hashCode() { 527 int result = super.hashCode(); 528 result = HashUtilities.hashCode(result, this.widthRadius); 529 return result; 530 } 531 532 } 533 534}