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:  * 
  42:  */
  43: 
  44: package org.jfree.chart.plot.dial;
  45: 
  46: import java.awt.BasicStroke;
  47: import java.awt.Color;
  48: import java.awt.Graphics2D;
  49: import java.awt.Paint;
  50: import java.awt.Stroke;
  51: import java.awt.geom.Arc2D;
  52: import java.awt.geom.GeneralPath;
  53: import java.awt.geom.Line2D;
  54: import java.awt.geom.Point2D;
  55: import java.awt.geom.Rectangle2D;
  56: import java.io.IOException;
  57: import java.io.ObjectInputStream;
  58: import java.io.ObjectOutputStream;
  59: import java.io.Serializable;
  60: 
  61: import org.jfree.chart.HashUtilities;
  62: import org.jfree.io.SerialUtilities;
  63: import org.jfree.util.PaintUtilities;
  64: import org.jfree.util.PublicCloneable;
  65: 
  66: /**
  67:  * A base class for the pointer in a {@link DialPlot}.
  68:  */
  69: public abstract class DialPointer extends AbstractDialLayer 
  70:         implements DialLayer, Cloneable, PublicCloneable, Serializable {
  71:     
  72:     /** The needle radius. */
  73:     double radius;
  74:     
  75:     /**
  76:      * The dataset index for the needle.
  77:      */
  78:     int datasetIndex;
  79:     
  80:     /** 
  81:      * Creates a new <code>DialPointer</code> instance.
  82:      */
  83:     protected DialPointer() {
  84:         this(0);
  85:     }
  86:     
  87:     /**
  88:      * Creates a new pointer for the specified dataset.
  89:      * 
  90:      * @param datasetIndex  the dataset index.
  91:      */
  92:     protected DialPointer(int datasetIndex) {
  93:         this.radius = 0.9;
  94:         this.datasetIndex = datasetIndex;
  95:     }
  96:     
  97:     /**
  98:      * Returns the dataset index that the pointer maps to.
  99:      * 
 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: }