Source for org.jfree.chart.plot.dial.DialPointer

   1: /* ===========================================================
   2:  * JFreeChart : a free chart library for the Java(tm) platform
   3:  * ===========================================================
   4:  *
   5:  * (C) Copyright 2000-2007, by Object Refinery Limited and Contributors.
   6:  *
   7:  * Project Info:  http://www.jfree.org/jfreechart/index.html
   8:  *
   9:  * This library is free software; you can redistribute it and/or modify it 
  10:  * under the terms of the GNU Lesser General Public License as published by 
  11:  * the Free Software Foundation; either version 2.1 of the License, or 
  12:  * (at your option) any later version.
  13:  *
  14:  * This library is distributed in the hope that it will be useful, but 
  15:  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
  16:  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
  17:  * License for more details.
  18:  *
  19:  * You should have received a copy of the GNU Lesser General Public
  20:  * License along with this library; if not, write to the Free Software
  21:  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
  22:  * USA.  
  23:  *
  24:  * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
  25:  * in the United States and other countries.]
  26:  *
  27:  * ----------------
  28:  * DialPointer.java
  29:  * ----------------
  30:  * (C) Copyright 2006-2007, by Object Refinery Limited.
  31:  *
  32:  * Original Author:  David Gilbert (for Object Refinery Limited);
  33:  * Contributor(s):   -;
  34:  *
  35:  * Changes
  36:  * -------
  37:  * 03-Nov-2006 : Version 1 (DG);
  38:  * 17-Oct-2007 : Added equals() overrides (DG);
  39:  * 24-Oct-2007 : Implemented PublicCloneable, changed default radius,
  40:  *               and added argument checks (DG);
  41:  * 23-Nov-2007 : Added fillPaint and outlinePaint attributes to 
  42:  *               DialPointer.Pointer (DG);
  43:  * 
  44:  */
  45: 
  46: package org.jfree.chart.plot.dial;
  47: 
  48: import java.awt.BasicStroke;
  49: import java.awt.Color;
  50: import java.awt.Graphics2D;
  51: import java.awt.Paint;
  52: import java.awt.Stroke;
  53: import java.awt.geom.Arc2D;
  54: import java.awt.geom.GeneralPath;
  55: import java.awt.geom.Line2D;
  56: import java.awt.geom.Point2D;
  57: import java.awt.geom.Rectangle2D;
  58: import java.io.IOException;
  59: import java.io.ObjectInputStream;
  60: import java.io.ObjectOutputStream;
  61: import java.io.Serializable;
  62: 
  63: import org.jfree.chart.HashUtilities;
  64: import org.jfree.io.SerialUtilities;
  65: import org.jfree.util.PaintUtilities;
  66: import org.jfree.util.PublicCloneable;
  67: 
  68: /**
  69:  * A base class for the pointer in a {@link DialPlot}.
  70:  * 
  71:  * @since 1.0.7
  72:  */
  73: public abstract class DialPointer extends AbstractDialLayer 
  74:         implements DialLayer, Cloneable, PublicCloneable, Serializable {
  75:     
  76:     /** The needle radius. */
  77:     double radius;
  78:     
  79:     /**
  80:      * The dataset index for the needle.
  81:      */
  82:     int datasetIndex;
  83:     
  84:     /** 
  85:      * Creates a new <code>DialPointer</code> instance.
  86:      */
  87:     protected DialPointer() {
  88:         this(0);
  89:     }
  90:     
  91:     /**
  92:      * Creates a new pointer for the specified dataset.
  93:      * 
  94:      * @param datasetIndex  the dataset index.
  95:      */
  96:     protected DialPointer(int datasetIndex) {
  97:         this.radius = 0.9;
  98:         this.datasetIndex = datasetIndex;
  99:     }
 100:     
 101:     /**
 102:      * Returns the dataset index that the pointer maps to.
 103:      * 
 104:      * @return The dataset index.
 105:      * 
 106:      * @see #getDatasetIndex()
 107:      */
 108:     public int getDatasetIndex() {
 109:         return this.datasetIndex;
 110:     }
 111:     
 112:     /**
 113:      * Sets the dataset index for the pointer and sends a 
 114:      * {@link DialLayerChangeEvent} to all registered listeners.
 115:      * 
 116:      * @param index  the index.
 117:      * 
 118:      * @see #getDatasetIndex()
 119:      */
 120:     public void setDatasetIndex(int index) {
 121:         this.datasetIndex = index;
 122:         notifyListeners(new DialLayerChangeEvent(this));
 123:     }
 124:     
 125:     /**
 126:      * Returns the radius of the pointer, as a percentage of the dial's
 127:      * framing rectangle.
 128:      * 
 129:      * @return The radius.
 130:      * 
 131:      * @see #setRadius(double)
 132:      */
 133:     public double getRadius() {
 134:         return this.radius;
 135:     }
 136:     
 137:     /**
 138:      * Sets the radius of the pointer and sends a 
 139:      * {@link DialLayerChangeEvent} to all registered listeners.
 140:      * 
 141:      * @param radius  the radius.
 142:      * 
 143:      * @see #getRadius()
 144:      */
 145:     public void setRadius(double radius) {
 146:         this.radius = radius;
 147:         notifyListeners(new DialLayerChangeEvent(this));
 148:     }
 149:     
 150:     /**
 151:      * Returns <code>true</code> to indicate that this layer should be 
 152:      * clipped within the dial window.
 153:      * 
 154:      * @return <code>true</code>.
 155:      */
 156:     public boolean isClippedToWindow() {
 157:         return true;
 158:     }
 159:     
 160:     /**
 161:      * Checks this instance for equality with an arbitrary object.
 162:      * 
 163:      * @param obj  the object (<code>null</code> not permitted).
 164:      * 
 165:      * @return A boolean.
 166:      */
 167:     public boolean equals(Object obj) {
 168:         if (obj == this) {
 169:             return true;
 170:         }
 171:         if (!(obj instanceof DialPointer)) {
 172:             return false;
 173:         }
 174:         DialPointer that = (DialPointer) obj;
 175:         if (this.datasetIndex != that.datasetIndex) {
 176:             return false;
 177:         }
 178:         if (this.radius != that.radius) {
 179:             return false;
 180:         }
 181:         return super.equals(obj);
 182:     }
 183:     
 184:     /**
 185:      * Returns a hash code.
 186:      * 
 187:      * @return A hash code.
 188:      */
 189:     public int hashCode() {
 190:         int result = 23;
 191:         result = HashUtilities.hashCode(result, this.radius);
 192:         return result;
 193:     }
 194:     
 195:     /**
 196:      * Returns a clone of the pointer.
 197:      * 
 198:      * @return a clone.
 199:      * 
 200:      * @throws CloneNotSupportedException if one of the attributes cannot
 201:      *     be cloned.
 202:      */
 203:     public Object clone() throws CloneNotSupportedException {
 204:         return super.clone();
 205:     }
 206: 
 207:     /**
 208:      * A dial pointer that draws a thin line (like a pin).
 209:      */
 210:     public static class Pin extends DialPointer {
 211:     
 212:         /** For serialization. */
 213:         static final long serialVersionUID = -8445860485367689750L;
 214: 
 215:         /** The paint. */
 216:         private transient Paint paint;
 217:     
 218:         /** The stroke. */
 219:         private transient Stroke stroke;
 220:         
 221:         /**
 222:          * Creates a new instance.
 223:          */
 224:         public Pin() {
 225:             this(0);
 226:         }
 227:         
 228:         /**
 229:          * Creates a new instance.
 230:          * 
 231:          * @param datasetIndex  the dataset index.
 232:          */
 233:         public Pin(int datasetIndex) {
 234:             super(datasetIndex);
 235:             this.paint = Color.red;
 236:             this.stroke = new BasicStroke(3.0f, BasicStroke.CAP_ROUND, 
 237:                     BasicStroke.JOIN_BEVEL);
 238:         }
 239:         
 240:         /**
 241:          * Returns the paint.
 242:          * 
 243:          * @return The paint (never <code>null</code>).
 244:          * 
 245:          * @see #setPaint(Paint)
 246:          */
 247:         public Paint getPaint() {
 248:             return this.paint;
 249:         }
 250:         
 251:         /**
 252:          * Sets the paint and sends a {@link DialLayerChangeEvent} to all 
 253:          * registered listeners.
 254:          * 
 255:          * @param paint  the paint (<code>null</code> not permitted).
 256:          * 
 257:          * @see #getPaint()
 258:          */
 259:         public void setPaint(Paint paint) {
 260:             if (paint == null) {
 261:                 throw new IllegalArgumentException("Null 'paint' argument.");
 262:             }
 263:             this.paint = paint;
 264:             notifyListeners(new DialLayerChangeEvent(this));
 265:         }
 266:         
 267:         /**
 268:          * Returns the stroke.
 269:          * 
 270:          * @return The stroke (never <code>null</code>).
 271:          * 
 272:          * @see #setStroke(Stroke)
 273:          */
 274:         public Stroke getStroke() {
 275:             return this.stroke;
 276:         }
 277:         
 278:         /**
 279:          * Sets the stroke and sends a {@link DialLayerChangeEvent} to all 
 280:          * registered listeners.
 281:          * 
 282:          * @param stroke  the stroke (<code>null</code> not permitted).
 283:          * 
 284:          * @see #getStroke()
 285:          */
 286:         public void setStroke(Stroke stroke) {
 287:             if (stroke == null) {
 288:                 throw new IllegalArgumentException("Null 'stroke' argument.");
 289:             }
 290:             this.stroke = stroke;
 291:             notifyListeners(new DialLayerChangeEvent(this));
 292:         }
 293:         
 294:         /**
 295:          * Draws the pointer.
 296:          * 
 297:          * @param g2  the graphics target.
 298:          * @param plot  the plot.
 299:          * @param frame  the dial's reference frame.
 300:          * @param view  the dial's view.
 301:          */
 302:         public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 
 303:             Rectangle2D view) {
 304:         
 305:             g2.setPaint(this.paint);
 306:             g2.setStroke(this.stroke);
 307:             Rectangle2D arcRect = DialPlot.rectangleByRadius(frame, 
 308:                     this.radius, this.radius);
 309: 
 310:             double value = plot.getValue(this.datasetIndex);
 311:             DialScale scale = plot.getScaleForDataset(this.datasetIndex);
 312:             double angle = scale.valueToAngle(value);
 313:         
 314:             Arc2D arc = new Arc2D.Double(arcRect, angle, 0, Arc2D.OPEN);
 315:             Point2D pt = arc.getEndPoint();
 316:         
 317:             Line2D line = new Line2D.Double(frame.getCenterX(), 
 318:                     frame.getCenterY(), pt.getX(), pt.getY());
 319:             g2.draw(line);
 320:         }
 321:         
 322:         /**
 323:          * Tests this pointer for equality with an arbitrary object.
 324:          * 
 325:          * @param obj  the object (<code>null</code> permitted).
 326:          * 
 327:          * @return A boolean.
 328:          */
 329:         public boolean equals(Object obj) {
 330:             if (obj == this) {
 331:                 return true;
 332:             }
 333:             if (!(obj instanceof DialPointer.Pin)) {
 334:                 return false;
 335:             }
 336:             DialPointer.Pin that = (DialPointer.Pin) obj;
 337:             if (!PaintUtilities.equal(this.paint, that.paint)) {
 338:                 return false;
 339:             }
 340:             if (!this.stroke.equals(that.stroke)) {
 341:                 return false;
 342:             }
 343:             return super.equals(obj);
 344:         }
 345:         
 346:         /**
 347:          * Returns a hash code for this instance.
 348:          * 
 349:          * @return A hash code.
 350:          */
 351:         public int hashCode() {
 352:             int result = super.hashCode();
 353:             result = HashUtilities.hashCode(result, this.paint);
 354:             result = HashUtilities.hashCode(result, this.stroke);
 355:             return result;
 356:         }
 357:         
 358:         /**
 359:          * Provides serialization support.
 360:          *
 361:          * @param stream  the output stream.
 362:          *
 363:          * @throws IOException  if there is an I/O error.
 364:          */
 365:         private void writeObject(ObjectOutputStream stream) throws IOException {
 366:             stream.defaultWriteObject();
 367:             SerialUtilities.writePaint(this.paint, stream);
 368:             SerialUtilities.writeStroke(this.stroke, stream);
 369:         }
 370: 
 371:         /**
 372:          * Provides serialization support.
 373:          *
 374:          * @param stream  the input stream.
 375:          *
 376:          * @throws IOException  if there is an I/O error.
 377:          * @throws ClassNotFoundException  if there is a classpath problem.
 378:          */
 379:         private void readObject(ObjectInputStream stream) 
 380:                 throws IOException, ClassNotFoundException {
 381:             stream.defaultReadObject();
 382:             this.paint = SerialUtilities.readPaint(stream);
 383:             this.stroke = SerialUtilities.readStroke(stream);
 384:         }
 385:         
 386:     }
 387:     
 388:     /**
 389:      * A dial pointer.
 390:      */
 391:     public static class Pointer extends DialPointer {
 392:         
 393:         /** For serialization. */
 394:         static final long serialVersionUID = -4180500011963176960L;
 395:         
 396:         /**
 397:          * The radius that defines the width of the pointer at the base.
 398:          */
 399:         private double widthRadius;
 400:     
 401:         /** 
 402:          * The fill paint.
 403:          * 
 404:          * @since 1.0.8
 405:          */
 406:         private transient Paint fillPaint;
 407:         
 408:         /** 
 409:          * The outline paint.
 410:          * 
 411:          * @since 1.0.8
 412:          */
 413:         private transient Paint outlinePaint;
 414: 
 415:         /**
 416:          * Creates a new instance.
 417:          */
 418:         public Pointer() {
 419:             this(0);
 420:         }
 421:         
 422:         /**
 423:          * Creates a new instance.
 424:          * 
 425:          * @param datasetIndex  the dataset index.
 426:          */
 427:         public Pointer(int datasetIndex) {
 428:             super(datasetIndex);
 429:             this.widthRadius = 0.05;
 430:             this.fillPaint = Color.gray;
 431:             this.outlinePaint = Color.black;
 432:         }
 433:         
 434:         /**
 435:          * Returns the width radius.
 436:          * 
 437:          * @return The width radius.
 438:          * 
 439:          * @see #setWidthRadius(double)
 440:          */
 441:         public double getWidthRadius() {
 442:             return this.widthRadius;
 443:         }
 444:         
 445:         /**
 446:          * Sets the width radius and sends a {@link DialLayerChangeEvent} to 
 447:          * all registered listeners.
 448:          * 
 449:          * @param radius  the radius
 450:          * 
 451:          * @see #getWidthRadius()
 452:          */
 453:         public void setWidthRadius(double radius) {
 454:             this.widthRadius = radius;
 455:             notifyListeners(new DialLayerChangeEvent(this));
 456:         }
 457:         
 458:         /**
 459:          * Returns the fill paint.
 460:          * 
 461:          * @return The paint (never <code>null</code>).
 462:          * 
 463:          * @see #setFillPaint(Paint)
 464:          * 
 465:          * @since 1.0.8
 466:          */
 467:         public Paint getFillPaint() {
 468:             return this.fillPaint;
 469:         }
 470:         
 471:         /**
 472:          * Sets the fill paint and sends a {@link DialLayerChangeEvent} to all 
 473:          * registered listeners.
 474:          * 
 475:          * @param paint  the paint (<code>null</code> not permitted).
 476:          * 
 477:          * @see #getFillPaint()
 478:          * 
 479:          * @since 1.0.8
 480:          */
 481:         public void setFillPaint(Paint paint) {
 482:             if (paint == null) {
 483:                 throw new IllegalArgumentException("Null 'paint' argument.");
 484:             }
 485:             this.fillPaint = paint;
 486:             notifyListeners(new DialLayerChangeEvent(this));
 487:         }
 488:         
 489:         /**
 490:          * Returns the outline paint.
 491:          * 
 492:          * @return The paint (never <code>null</code>).
 493:          * 
 494:          * @see #setOutlinePaint(Paint)
 495:          * 
 496:          * @since 1.0.8
 497:          */
 498:         public Paint getOutlinePaint() {
 499:             return this.outlinePaint;
 500:         }
 501:         
 502:         /**
 503:          * Sets the outline paint and sends a {@link DialLayerChangeEvent} to 
 504:          * all registered listeners.
 505:          * 
 506:          * @param paint  the paint (<code>null</code> not permitted).
 507:          * 
 508:          * @see #getOutlinePaint()
 509:          * 
 510:          * @since 1.0.8
 511:          */
 512:         public void setOutlinePaint(Paint paint) {
 513:             if (paint == null) {
 514:                 throw new IllegalArgumentException("Null 'paint' argument.");
 515:             }
 516:             this.outlinePaint = paint;
 517:             notifyListeners(new DialLayerChangeEvent(this));
 518:         }
 519:         
 520:         /**
 521:          * Draws the pointer.
 522:          * 
 523:          * @param g2  the graphics target.
 524:          * @param plot  the plot.
 525:          * @param frame  the dial's reference frame.
 526:          * @param view  the dial's view.
 527:          */
 528:         public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 
 529:                 Rectangle2D view) {
 530:         
 531:             g2.setPaint(Color.blue);
 532:             g2.setStroke(new BasicStroke(1.0f));
 533:             Rectangle2D lengthRect = DialPlot.rectangleByRadius(frame, 
 534:                     this.radius, this.radius);
 535:             Rectangle2D widthRect = DialPlot.rectangleByRadius(frame, 
 536:                     this.widthRadius, this.widthRadius);
 537:             double value = plot.getValue(this.datasetIndex);
 538:             DialScale scale = plot.getScaleForDataset(this.datasetIndex);
 539:             double angle = scale.valueToAngle(value);
 540:         
 541:             Arc2D arc1 = new Arc2D.Double(lengthRect, angle, 0, Arc2D.OPEN);
 542:             Point2D pt1 = arc1.getEndPoint();
 543:             Arc2D arc2 = new Arc2D.Double(widthRect, angle - 90.0, 180.0, 
 544:                     Arc2D.OPEN);
 545:             Point2D pt2 = arc2.getStartPoint();
 546:             Point2D pt3 = arc2.getEndPoint();
 547:             Arc2D arc3 = new Arc2D.Double(widthRect, angle - 180.0, 0.0, 
 548:                     Arc2D.OPEN);
 549:             Point2D pt4 = arc3.getStartPoint();
 550:         
 551:             GeneralPath gp = new GeneralPath();
 552:             gp.moveTo((float) pt1.getX(), (float) pt1.getY());
 553:             gp.lineTo((float) pt2.getX(), (float) pt2.getY());
 554:             gp.lineTo((float) pt4.getX(), (float) pt4.getY());
 555:             gp.lineTo((float) pt3.getX(), (float) pt3.getY());
 556:             gp.closePath();
 557:             g2.setPaint(this.fillPaint);
 558:             g2.fill(gp);
 559:         
 560:             g2.setPaint(this.outlinePaint);
 561:             Line2D line = new Line2D.Double(frame.getCenterX(), 
 562:                     frame.getCenterY(), pt1.getX(), pt1.getY());
 563:             g2.draw(line);
 564:         
 565:             line.setLine(pt2, pt3);
 566:             g2.draw(line);
 567:         
 568:             line.setLine(pt3, pt1);
 569:             g2.draw(line);
 570:         
 571:             line.setLine(pt2, pt1);
 572:             g2.draw(line);
 573:         
 574:             line.setLine(pt2, pt4);
 575:             g2.draw(line);
 576: 
 577:             line.setLine(pt3, pt4);
 578:             g2.draw(line);
 579:         }
 580:         
 581:         /**
 582:          * Tests this pointer for equality with an arbitrary object.
 583:          * 
 584:          * @param obj  the object (<code>null</code> permitted).
 585:          * 
 586:          * @return A boolean.
 587:          */
 588:         public boolean equals(Object obj) {
 589:             if (obj == this) {
 590:                 return true;
 591:             }
 592:             if (!(obj instanceof DialPointer.Pointer)) {
 593:                 return false;
 594:             }
 595:             DialPointer.Pointer that = (DialPointer.Pointer) obj;
 596:             
 597:             if (this.widthRadius != that.widthRadius) {
 598:                 return false;
 599:             }
 600:             if (!PaintUtilities.equal(this.fillPaint, that.fillPaint)) {
 601:                 return false;
 602:             }
 603:             if (!PaintUtilities.equal(this.outlinePaint, that.outlinePaint)) {
 604:                 return false;
 605:             }
 606:             return super.equals(obj);
 607:         }
 608:         
 609:         /**
 610:          * Returns a hash code for this instance.
 611:          * 
 612:          * @return A hash code.
 613:          */
 614:         public int hashCode() {
 615:             int result = super.hashCode();
 616:             result = HashUtilities.hashCode(result, this.widthRadius);
 617:             result = HashUtilities.hashCode(result, this.fillPaint);
 618:             result = HashUtilities.hashCode(result, this.outlinePaint);
 619:             return result;
 620:         }
 621:         
 622:         /**
 623:          * Provides serialization support.
 624:          *
 625:          * @param stream  the output stream.
 626:          *
 627:          * @throws IOException  if there is an I/O error.
 628:          */
 629:         private void writeObject(ObjectOutputStream stream) throws IOException {
 630:             stream.defaultWriteObject();
 631:             SerialUtilities.writePaint(this.fillPaint, stream);
 632:             SerialUtilities.writePaint(this.outlinePaint, stream);
 633:         }
 634: 
 635:         /**
 636:          * Provides serialization support.
 637:          *
 638:          * @param stream  the input stream.
 639:          *
 640:          * @throws IOException  if there is an I/O error.
 641:          * @throws ClassNotFoundException  if there is a classpath problem.
 642:          */
 643:         private void readObject(ObjectInputStream stream) 
 644:                 throws IOException, ClassNotFoundException {
 645:             stream.defaultReadObject();
 646:             this.fillPaint = SerialUtilities.readPaint(stream);
 647:             this.outlinePaint = SerialUtilities.readPaint(stream);
 648:         }
 649:        
 650:     }
 651: 
 652: }