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