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 * DialValueIndicator.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 : Updated equals() (DG); 039 * 24-Oct-2007 : Added default constructor and missing event notification (DG); 040 * 041 */ 042 043package org.jfree.chart.plot.dial; 044 045import java.awt.BasicStroke; 046import java.awt.Color; 047import java.awt.Font; 048import java.awt.FontMetrics; 049import java.awt.Graphics2D; 050import java.awt.Paint; 051import java.awt.Stroke; 052import java.awt.geom.Arc2D; 053import java.awt.geom.Point2D; 054import java.awt.geom.Rectangle2D; 055import java.io.IOException; 056import java.io.ObjectInputStream; 057import java.io.ObjectOutputStream; 058import java.io.Serializable; 059import java.text.DecimalFormat; 060import java.text.NumberFormat; 061 062import org.jfree.chart.HashUtilities; 063import org.jfree.io.SerialUtilities; 064import org.jfree.text.TextUtilities; 065import org.jfree.ui.RectangleAnchor; 066import org.jfree.ui.RectangleInsets; 067import org.jfree.ui.Size2D; 068import org.jfree.ui.TextAnchor; 069import org.jfree.util.PaintUtilities; 070import org.jfree.util.PublicCloneable; 071 072/** 073 * A value indicator for a {@link DialPlot}. 074 */ 075public class DialValueIndicator extends AbstractDialLayer implements DialLayer, 076 Cloneable, PublicCloneable, Serializable { 077 078 /** For serialization. */ 079 static final long serialVersionUID = 803094354130942585L; 080 081 /** The dataset index. */ 082 private int datasetIndex; 083 084 /** The angle that defines the anchor point. */ 085 private double angle; 086 087 /** The radius that defines the anchor point. */ 088 private double radius; 089 090 /** The frame anchor. */ 091 private RectangleAnchor frameAnchor; 092 093 /** The template value. */ 094 private Number templateValue; 095 096 /** The formatter. */ 097 private NumberFormat formatter; 098 099 /** The font. */ 100 private Font font; 101 102 /** The paint. */ 103 private transient Paint paint; 104 105 /** The background paint. */ 106 private transient Paint backgroundPaint; 107 108 /** The outline stroke. */ 109 private transient Stroke outlineStroke; 110 111 /** The outline paint. */ 112 private transient Paint outlinePaint; 113 114 /** The insets. */ 115 private RectangleInsets insets; 116 117 /** The value anchor. */ 118 private RectangleAnchor valueAnchor; 119 120 /** The text anchor for displaying the value. */ 121 private TextAnchor textAnchor; 122 123 /** 124 * Creates a new instance of <code>DialValueIndicator</code>. 125 */ 126 public DialValueIndicator() { 127 this(0); 128 } 129 130 /** 131 * Creates a new instance of <code>DialValueIndicator</code>. 132 * 133 * @param datasetIndex the dataset index. 134 */ 135 public DialValueIndicator(int datasetIndex) { 136 this.datasetIndex = datasetIndex; 137 this.angle = -90.0; 138 this.radius = 0.3; 139 this.frameAnchor = RectangleAnchor.CENTER; 140 this.templateValue = new Double(100.0); 141 this.formatter = new DecimalFormat("0.0"); 142 this.font = new Font("Dialog", Font.BOLD, 14); 143 this.paint = Color.black; 144 this.backgroundPaint = Color.white; 145 this.outlineStroke = new BasicStroke(1.0f); 146 this.outlinePaint = Color.blue; 147 this.insets = new RectangleInsets(4, 4, 4, 4); 148 this.valueAnchor = RectangleAnchor.RIGHT; 149 this.textAnchor = TextAnchor.CENTER_RIGHT; 150 } 151 152 /** 153 * Returns the index of the dataset from which this indicator fetches its 154 * current value. 155 * 156 * @return The dataset index. 157 * 158 * @see #setDatasetIndex(int) 159 */ 160 public int getDatasetIndex() { 161 return this.datasetIndex; 162 } 163 164 /** 165 * Sets the dataset index and sends a {@link DialLayerChangeEvent} to all 166 * registered listeners. 167 * 168 * @param index the index. 169 * 170 * @see #getDatasetIndex() 171 */ 172 public void setDatasetIndex(int index) { 173 this.datasetIndex = index; 174 notifyListeners(new DialLayerChangeEvent(this)); 175 } 176 177 /** 178 * Returns the angle for the anchor point. The angle is specified in 179 * degrees using the same orientation as Java's <code>Arc2D</code> class. 180 * 181 * @return The angle (in degrees). 182 * 183 * @see #setAngle(double) 184 */ 185 public double getAngle() { 186 return this.angle; 187 } 188 189 /** 190 * Sets the angle for the anchor point and sends a 191 * {@link DialLayerChangeEvent} to all registered listeners. 192 * 193 * @param angle the angle (in degrees). 194 * 195 * @see #getAngle() 196 */ 197 public void setAngle(double angle) { 198 this.angle = angle; 199 notifyListeners(new DialLayerChangeEvent(this)); 200 } 201 202 /** 203 * Returns the radius. 204 * 205 * @return The radius. 206 * 207 * @see #setRadius(double) 208 */ 209 public double getRadius() { 210 return this.radius; 211 } 212 213 /** 214 * Sets the radius and sends a {@link DialLayerChangeEvent} to all 215 * registered listeners. 216 * 217 * @param radius the radius. 218 * 219 * @see #getRadius() 220 */ 221 public void setRadius(double radius) { 222 this.radius = radius; 223 notifyListeners(new DialLayerChangeEvent(this)); 224 } 225 226 /** 227 * Returns the frame anchor. 228 * 229 * @return The frame anchor. 230 * 231 * @see #setFrameAnchor(RectangleAnchor) 232 */ 233 public RectangleAnchor getFrameAnchor() { 234 return this.frameAnchor; 235 } 236 237 /** 238 * Sets the frame anchor and sends a {@link DialLayerChangeEvent} to all 239 * registered listeners. 240 * 241 * @param anchor the anchor (<code>null</code> not permitted). 242 * 243 * @see #getFrameAnchor() 244 */ 245 public void setFrameAnchor(RectangleAnchor anchor) { 246 if (anchor == null) { 247 throw new IllegalArgumentException("Null 'anchor' argument."); 248 } 249 this.frameAnchor = anchor; 250 notifyListeners(new DialLayerChangeEvent(this)); 251 } 252 253 /** 254 * Returns the template value. 255 * 256 * @return The template value (never <code>null</code>). 257 * 258 * @see #setTemplateValue(Number) 259 */ 260 public Number getTemplateValue() { 261 return this.templateValue; 262 } 263 264 /** 265 * Sets the template value and sends a {@link DialLayerChangeEvent} to 266 * all registered listeners. 267 * 268 * @param value the value (<code>null</code> not permitted). 269 * 270 * @see #setTemplateValue(Number) 271 */ 272 public void setTemplateValue(Number value) { 273 if (value == null) { 274 throw new IllegalArgumentException("Null 'value' argument."); 275 } 276 this.templateValue = value; 277 notifyListeners(new DialLayerChangeEvent(this)); 278 } 279 280 /** 281 * Returns the formatter used to format the value. 282 * 283 * @return The formatter (never <code>null</code>). 284 * 285 * @see #setNumberFormat(NumberFormat) 286 */ 287 public NumberFormat getNumberFormat() { 288 return this.formatter; 289 } 290 291 /** 292 * Sets the formatter used to format the value and sends a 293 * {@link DialLayerChangeEvent} to all registered listeners. 294 * 295 * @param formatter the formatter (<code>null</code> not permitted). 296 * 297 * @see #getNumberFormat() 298 */ 299 public void setNumberFormat(NumberFormat formatter) { 300 if (formatter == null) { 301 throw new IllegalArgumentException("Null 'formatter' argument."); 302 } 303 this.formatter = formatter; 304 notifyListeners(new DialLayerChangeEvent(this)); 305 } 306 307 /** 308 * Returns the font. 309 * 310 * @return The font (never <code>null</code>). 311 * 312 * @see #getFont() 313 */ 314 public Font getFont() { 315 return this.font; 316 } 317 318 /** 319 * Sets the font and sends a {@link DialLayerChangeEvent} to all registered 320 * listeners. 321 * 322 * @param font the font (<code>null</code> not permitted). 323 */ 324 public void setFont(Font font) { 325 if (font == null) { 326 throw new IllegalArgumentException("Null 'font' argument."); 327 } 328 this.font = font; 329 notifyListeners(new DialLayerChangeEvent(this)); 330 } 331 332 /** 333 * Returns the paint. 334 * 335 * @return The paint (never <code>null</code>). 336 * 337 * @see #setPaint(Paint) 338 */ 339 public Paint getPaint() { 340 return this.paint; 341 } 342 343 /** 344 * Sets the paint and sends a {@link DialLayerChangeEvent} to all 345 * registered listeners. 346 * 347 * @param paint the paint (<code>null</code> not permitted). 348 * 349 * @see #getPaint() 350 */ 351 public void setPaint(Paint paint) { 352 if (paint == null) { 353 throw new IllegalArgumentException("Null 'paint' argument."); 354 } 355 this.paint = paint; 356 notifyListeners(new DialLayerChangeEvent(this)); 357 } 358 359 /** 360 * Returns the background paint. 361 * 362 * @return The background paint. 363 * 364 * @see #setBackgroundPaint(Paint) 365 */ 366 public Paint getBackgroundPaint() { 367 return this.backgroundPaint; 368 } 369 370 /** 371 * Sets the background paint and sends a {@link DialLayerChangeEvent} to 372 * all registered listeners. 373 * 374 * @param paint the paint (<code>null</code> not permitted). 375 * 376 * @see #getBackgroundPaint() 377 */ 378 public void setBackgroundPaint(Paint paint) { 379 if (paint == null) { 380 throw new IllegalArgumentException("Null 'paint' argument."); 381 } 382 this.backgroundPaint = paint; 383 notifyListeners(new DialLayerChangeEvent(this)); 384 } 385 386 /** 387 * Returns the outline stroke. 388 * 389 * @return The outline stroke (never <code>null</code>). 390 * 391 * @see #setOutlineStroke(Stroke) 392 */ 393 public Stroke getOutlineStroke() { 394 return this.outlineStroke; 395 } 396 397 /** 398 * Sets the outline stroke and sends a {@link DialLayerChangeEvent} to 399 * all registered listeners. 400 * 401 * @param stroke the stroke (<code>null</code> not permitted). 402 * 403 * @see #getOutlineStroke() 404 */ 405 public void setOutlineStroke(Stroke stroke) { 406 if (stroke == null) { 407 throw new IllegalArgumentException("Null 'stroke' argument."); 408 } 409 this.outlineStroke = stroke; 410 notifyListeners(new DialLayerChangeEvent(this)); 411 } 412 413 /** 414 * Returns the outline paint. 415 * 416 * @return The outline paint (never <code>null</code>). 417 * 418 * @see #setOutlinePaint(Paint) 419 */ 420 public Paint getOutlinePaint() { 421 return this.outlinePaint; 422 } 423 424 /** 425 * Sets the outline paint and sends a {@link DialLayerChangeEvent} to all 426 * registered listeners. 427 * 428 * @param paint the paint (<code>null</code> not permitted). 429 * 430 * @see #getOutlinePaint() 431 */ 432 public void setOutlinePaint(Paint paint) { 433 if (paint == null) { 434 throw new IllegalArgumentException("Null 'paint' argument."); 435 } 436 this.outlinePaint = paint; 437 notifyListeners(new DialLayerChangeEvent(this)); 438 } 439 440 /** 441 * Returns the insets. 442 * 443 * @return The insets (never <code>null</code>). 444 * 445 * @see #setInsets(RectangleInsets) 446 */ 447 public RectangleInsets getInsets() { 448 return this.insets; 449 } 450 451 /** 452 * Sets the insets and sends a {@link DialLayerChangeEvent} to all 453 * registered listeners. 454 * 455 * @param insets the insets (<code>null</code> not permitted). 456 * 457 * @see #getInsets() 458 */ 459 public void setInsets(RectangleInsets insets) { 460 if (insets == null) { 461 throw new IllegalArgumentException("Null 'insets' argument."); 462 } 463 this.insets = insets; 464 notifyListeners(new DialLayerChangeEvent(this)); 465 } 466 467 /** 468 * Returns the value anchor. 469 * 470 * @return The value anchor (never <code>null</code>). 471 * 472 * @see #setValueAnchor(RectangleAnchor) 473 */ 474 public RectangleAnchor getValueAnchor() { 475 return this.valueAnchor; 476 } 477 478 /** 479 * Sets the value anchor and sends a {@link DialLayerChangeEvent} to all 480 * registered listeners. 481 * 482 * @param anchor the anchor (<code>null</code> not permitted). 483 * 484 * @see #getValueAnchor() 485 */ 486 public void setValueAnchor(RectangleAnchor anchor) { 487 if (anchor == null) { 488 throw new IllegalArgumentException("Null 'anchor' argument."); 489 } 490 this.valueAnchor = anchor; 491 notifyListeners(new DialLayerChangeEvent(this)); 492 } 493 494 /** 495 * Returns the text anchor. 496 * 497 * @return The text anchor (never <code>null</code>). 498 * 499 * @see #setTextAnchor(TextAnchor) 500 */ 501 public TextAnchor getTextAnchor() { 502 return this.textAnchor; 503 } 504 505 /** 506 * Sets the text anchor and sends a {@link DialLayerChangeEvent} to all 507 * registered listeners. 508 * 509 * @param anchor the anchor (<code>null</code> not permitted). 510 * 511 * @see #getTextAnchor() 512 */ 513 public void setTextAnchor(TextAnchor anchor) { 514 if (anchor == null) { 515 throw new IllegalArgumentException("Null 'anchor' argument."); 516 } 517 this.textAnchor = anchor; 518 notifyListeners(new DialLayerChangeEvent(this)); 519 } 520 521 /** 522 * Returns <code>true</code> to indicate that this layer should be 523 * clipped within the dial window. 524 * 525 * @return <code>true</code>. 526 */ 527 public boolean isClippedToWindow() { 528 return true; 529 } 530 531 /** 532 * Draws the background to the specified graphics device. If the dial 533 * frame specifies a window, the clipping region will already have been 534 * set to this window before this method is called. 535 * 536 * @param g2 the graphics device (<code>null</code> not permitted). 537 * @param plot the plot (ignored here). 538 * @param frame the dial frame (ignored here). 539 * @param view the view rectangle (<code>null</code> not permitted). 540 */ 541 public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 542 Rectangle2D view) { 543 544 // work out the anchor point 545 Rectangle2D f = DialPlot.rectangleByRadius(frame, this.radius, 546 this.radius); 547 Arc2D arc = new Arc2D.Double(f, this.angle, 0.0, Arc2D.OPEN); 548 Point2D pt = arc.getStartPoint(); 549 550 // calculate the bounds of the template value 551 FontMetrics fm = g2.getFontMetrics(this.font); 552 String s = this.formatter.format(this.templateValue); 553 Rectangle2D tb = TextUtilities.getTextBounds(s, g2, fm); 554 555 // align this rectangle to the frameAnchor 556 Rectangle2D bounds = RectangleAnchor.createRectangle(new Size2D( 557 tb.getWidth(), tb.getHeight()), pt.getX(), pt.getY(), 558 this.frameAnchor); 559 560 // add the insets 561 Rectangle2D fb = this.insets.createOutsetRectangle(bounds); 562 563 // draw the background 564 g2.setPaint(this.backgroundPaint); 565 g2.fill(fb); 566 567 // draw the border 568 g2.setStroke(this.outlineStroke); 569 g2.setPaint(this.outlinePaint); 570 g2.draw(fb); 571 572 573 // now find the text anchor point 574 double value = plot.getValue(this.datasetIndex); 575 String valueStr = this.formatter.format(value); 576 Point2D pt2 = RectangleAnchor.coordinates(bounds, this.valueAnchor); 577 g2.setPaint(this.paint); 578 g2.setFont(this.font); 579 TextUtilities.drawAlignedString(valueStr, g2, (float) pt2.getX(), 580 (float) pt2.getY(), this.textAnchor); 581 582 } 583 584 /** 585 * Tests this instance for equality with an arbitrary object. 586 * 587 * @param obj the object (<code>null</code> permitted). 588 * 589 * @return A boolean. 590 */ 591 public boolean equals(Object obj) { 592 if (obj == this) { 593 return true; 594 } 595 if (!(obj instanceof DialValueIndicator)) { 596 return false; 597 } 598 DialValueIndicator that = (DialValueIndicator) obj; 599 if (this.datasetIndex != that.datasetIndex) { 600 return false; 601 } 602 if (this.angle != that.angle) { 603 return false; 604 } 605 if (this.radius != that.radius) { 606 return false; 607 } 608 if (!this.frameAnchor.equals(that.frameAnchor)) { 609 return false; 610 } 611 if (!this.templateValue.equals(that.templateValue)) { 612 return false; 613 } 614 if (!this.font.equals(that.font)) { 615 return false; 616 } 617 if (!PaintUtilities.equal(this.paint, that.paint)) { 618 return false; 619 } 620 if (!PaintUtilities.equal(this.backgroundPaint, that.backgroundPaint)) { 621 return false; 622 } 623 if (!this.outlineStroke.equals(that.outlineStroke)) { 624 return false; 625 } 626 if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) { 627 return false; 628 } 629 if (!this.insets.equals(that.insets)) { 630 return false; 631 } 632 if (!this.valueAnchor.equals(that.valueAnchor)) { 633 return false; 634 } 635 if (!this.textAnchor.equals(that.textAnchor)) { 636 return false; 637 } 638 639 return super.equals(obj); 640 } 641 642 /** 643 * Returns a hash code for this instance. 644 * 645 * @return The hash code. 646 */ 647 public int hashCode() { 648 int result = 193; 649 result = 37 * result + HashUtilities.hashCodeForPaint(this.paint); 650 result = 37 * result + HashUtilities.hashCodeForPaint( 651 this.backgroundPaint); 652 result = 37 * result + HashUtilities.hashCodeForPaint( 653 this.outlinePaint); 654 result = 37 * result + this.outlineStroke.hashCode(); 655 return result; 656 } 657 658 /** 659 * Returns a clone of this instance. 660 * 661 * @return The clone. 662 * 663 * @throws CloneNotSupportedException if some attribute of this instance 664 * cannot be cloned. 665 */ 666 public Object clone() throws CloneNotSupportedException { 667 return super.clone(); 668 } 669 670 /** 671 * Provides serialization support. 672 * 673 * @param stream the output stream. 674 * 675 * @throws IOException if there is an I/O error. 676 */ 677 private void writeObject(ObjectOutputStream stream) throws IOException { 678 stream.defaultWriteObject(); 679 SerialUtilities.writePaint(this.paint, stream); 680 SerialUtilities.writePaint(this.backgroundPaint, stream); 681 SerialUtilities.writePaint(this.outlinePaint, stream); 682 SerialUtilities.writeStroke(this.outlineStroke, stream); 683 } 684 685 /** 686 * Provides serialization support. 687 * 688 * @param stream the input stream. 689 * 690 * @throws IOException if there is an I/O error. 691 * @throws ClassNotFoundException if there is a classpath problem. 692 */ 693 private void readObject(ObjectInputStream stream) 694 throws IOException, ClassNotFoundException { 695 stream.defaultReadObject(); 696 this.paint = SerialUtilities.readPaint(stream); 697 this.backgroundPaint = SerialUtilities.readPaint(stream); 698 this.outlinePaint = SerialUtilities.readPaint(stream); 699 this.outlineStroke = SerialUtilities.readStroke(stream); 700 } 701 702}