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

   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:  * StandardDialScale.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-Nov-2006 : Added flags for tick label visibility (DG);
  39:  * 24-Oct-2007 : Added tick label formatter (DG);
  40:  * 
  41:  */
  42: 
  43: package org.jfree.chart.plot.dial;
  44: 
  45: import java.awt.BasicStroke;
  46: import java.awt.Color;
  47: import java.awt.Font;
  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.Line2D;
  53: import java.awt.geom.Point2D;
  54: import java.awt.geom.Rectangle2D;
  55: import java.io.IOException;
  56: import java.io.ObjectInputStream;
  57: import java.io.ObjectOutputStream;
  58: import java.io.Serializable;
  59: import java.text.DecimalFormat;
  60: import java.text.NumberFormat;
  61: 
  62: import org.jfree.io.SerialUtilities;
  63: import org.jfree.text.TextUtilities;
  64: import org.jfree.ui.TextAnchor;
  65: import org.jfree.util.PaintUtilities;
  66: import org.jfree.util.PublicCloneable;
  67: 
  68: /**
  69:  * A scale for a {@link DialPlot}.
  70:  */
  71: public class StandardDialScale extends AbstractDialLayer implements DialScale, 
  72:         Cloneable, PublicCloneable, Serializable {
  73:     
  74:     /** For serialization. */
  75:     static final long serialVersionUID = 3715644629665918516L;
  76:     
  77:     /** The minimum data value for the scale. */
  78:     private double lowerBound;
  79:     
  80:     /** The maximum data value for the scale. */
  81:     private double upperBound;
  82:     
  83:     /** 
  84:      * The start angle for the scale display, in degrees (using the same
  85:      * encoding as Arc2D). 
  86:      */
  87:     private double startAngle;
  88:     
  89:     /** The extent of the scale display. */
  90:     private double extent;
  91:     
  92:     /** 
  93:      * The factor (in the range 0.0 to 1.0) that determines the outside limit
  94:      * of the tick marks.
  95:      */
  96:     private double tickRadius;
  97: 
  98:     /**
  99:      * The increment (in data units) between major tick marks. 
 100:      */
 101:     private double majorTickIncrement;
 102: 
 103:     /**
 104:      * The factor that is subtracted from the tickRadius to determine the
 105:      * inner point of the major ticks.
 106:      */
 107:     private double majorTickLength;    
 108:     
 109:     /**
 110:      * The paint to use for major tick marks.  This field is transient because
 111:      * it requires special handling for serialization.
 112:      */
 113:     private transient Paint majorTickPaint;
 114:     
 115:     /**
 116:      * The stroke to use for major tick marks.  This field is transient because
 117:      * it requires special handling for serialization.
 118:      */
 119:     private transient Stroke majorTickStroke;
 120: 
 121:     /**
 122:      * The number of minor ticks between each major tick.
 123:      */
 124:     private int minorTickCount;
 125:     
 126:     /**
 127:      * The factor that is subtracted from the tickRadius to determine the
 128:      * inner point of the minor ticks.
 129:      */
 130:     private double minorTickLength;
 131:     
 132:     /**
 133:      * The paint to use for minor tick marks.  This field is transient because
 134:      * it requires special handling for serialization.
 135:      */
 136:     private transient Paint minorTickPaint;
 137:     
 138:     /**
 139:      * The stroke to use for minor tick marks.  This field is transient because
 140:      * it requires special handling for serialization.
 141:      */
 142:     private transient Stroke minorTickStroke;
 143: 
 144:     /**
 145:      * The tick label offset.
 146:      */
 147:     private double tickLabelOffset;
 148:     
 149:     /** 
 150:      * The tick label font.
 151:      */
 152:     private Font tickLabelFont;
 153:     
 154:     /** 
 155:      * A flag that controls whether or not the tick labels are 
 156:      * displayed. 
 157:      */
 158:     private boolean tickLabelsVisible;
 159:     
 160:     /**
 161:      * The number formatter for the tick labels.
 162:      */
 163:     private NumberFormat tickLabelFormatter;
 164:     
 165:     /**
 166:      * A flag that controls whether or not the first tick label is
 167:      * displayed.
 168:      */
 169:     private boolean firstTickLabelVisible;
 170:     
 171:     /**
 172:      * The tick label paint.  This field is transient because it requires 
 173:      * special handling for serialization.
 174:      */
 175:     private transient Paint tickLabelPaint;
 176:     
 177:     /** 
 178:      * Creates a new instance of DialScale.
 179:      */
 180:     public StandardDialScale() {
 181:         this(0.0, 100.0, 175, -170, 10.0, 4);
 182:     }
 183:     
 184:     /**
 185:      * Creates a new instance.
 186:      * 
 187:      * @param lowerBound  the lower bound of the scale.
 188:      * @param upperBound  the upper bound of the scale.
 189:      * @param startAngle  the start angle (in degrees, using the same 
 190:      *     orientation as Java's <code>Arc2D</code> class).
 191:      * @param extent  the extent (in degrees, counter-clockwise).
 192:      * @param majorTickIncrement  the interval between major tick marks
 193:      * @param minorTickCount  the number of minor ticks between major tick
 194:      *          marks.
 195:      */
 196:     public StandardDialScale(double lowerBound, double upperBound, 
 197:             double startAngle, double extent, double majorTickIncrement, 
 198:             int minorTickCount) {
 199:         this.startAngle = startAngle;
 200:         this.extent = extent;
 201:         this.lowerBound = lowerBound;
 202:         this.upperBound = upperBound;
 203:         this.tickRadius = 0.70;
 204:         this.tickLabelsVisible = true;
 205:         this.tickLabelFormatter = new DecimalFormat("0.0");
 206:         this.firstTickLabelVisible = true;
 207:         this.tickLabelFont = new Font("Dialog", Font.BOLD, 16);
 208:         this.tickLabelPaint = Color.blue;
 209:         this.tickLabelOffset = 0.10;
 210:         this.majorTickIncrement = majorTickIncrement;
 211:         this.majorTickLength = 0.04;
 212:         this.majorTickPaint = Color.black;
 213:         this.majorTickStroke = new BasicStroke(3.0f);
 214:         this.minorTickCount = minorTickCount;
 215:         this.minorTickLength = 0.02;
 216:         this.minorTickPaint = Color.black;
 217:         this.minorTickStroke = new BasicStroke(1.0f);
 218:     }
 219:     
 220:     /**
 221:      * Returns the start angle for the scale (in degrees using the same 
 222:      * orientation as Java's <code>Arc2D</code> class).
 223:      * 
 224:      * @return The start angle.
 225:      * 
 226:      * @see #setStartAngle(double)
 227:      */
 228:     public double getStartAngle() {
 229:         return this.startAngle;
 230:     }
 231:     
 232:     /**
 233:      * Sets the start angle for the scale and sends a 
 234:      * {@link DialLayerChangeEvent} to all registered listeners.
 235:      * 
 236:      * @param angle  the angle (in degrees).
 237:      * 
 238:      * @see #getStartAngle()
 239:      */
 240:     public void setStartAngle(double angle) {
 241:         this.startAngle = angle;
 242:         notifyListeners(new DialLayerChangeEvent(this));
 243:     }
 244:     
 245:     /**
 246:      * Returns the extent.
 247:      * 
 248:      * @return The extent.
 249:      * 
 250:      * @see #setExtent(double)
 251:      */
 252:     public double getExtent() {
 253:         return this.extent;
 254:     }
 255:     
 256:     /**
 257:      * Sets the extent and sends a {@link DialLayerChangeEvent} to all 
 258:      * registered listeners.
 259:      * 
 260:      * @param extent  the extent.
 261:      * 
 262:      * @see #getExtent()
 263:      */
 264:     public void setExtent(double extent) {
 265:         this.extent = extent;
 266:         notifyListeners(new DialLayerChangeEvent(this));
 267:     }
 268:     
 269:     /**
 270:      * Returns the radius (as a percentage of the maximum space available) of
 271:      * the outer limit of the tick marks.
 272:      *
 273:      * @return The tick radius.
 274:      *
 275:      * @see #setTickRadius(double)
 276:      */
 277:     public double getTickRadius() {
 278:         return this.tickRadius;
 279:     }
 280:     
 281:     /**
 282:      * Sets the tick radius and sends a {@link DialLayerChangeEvent} to all
 283:      * registered listeners.
 284:      *
 285:      * @param radius  the radius.
 286:      *
 287:      * @see #getTickRadius()
 288:      */
 289:     public void setTickRadius(double radius) {
 290:         if (radius <= 0.0) {
 291:             throw new IllegalArgumentException(
 292:                     "The 'radius' must be positive.");
 293:         }
 294:         this.tickRadius = radius;
 295:         notifyListeners(new DialLayerChangeEvent(this));
 296:     }
 297:     
 298:     /**
 299:      * Returns the increment (in data units) between major tick labels.
 300:      *
 301:      * @return The increment between major tick labels.
 302:      *
 303:      * @see #setMajorTickIncrement(double)
 304:      */
 305:     public double getMajorTickIncrement() {
 306:         return this.majorTickIncrement;
 307:     }
 308:     
 309:     /**
 310:      * Sets the increment (in data units) between major tick labels and sends a
 311:      * {@link DialLayerChangeEvent} to all registered listeners.
 312:      *
 313:      * @param increment  the increment.
 314:      *
 315:      * @see #getMajorTickIncrement()
 316:      */
 317:     public void setMajorTickIncrement(double increment) {
 318:         if (increment <= 0.0) {
 319:             throw new IllegalArgumentException(
 320:                     "The 'increment' must be positive.");
 321:         }
 322:         this.majorTickIncrement = increment;
 323:         notifyListeners(new DialLayerChangeEvent(this));
 324:     }
 325:     
 326:     /**
 327:      * Returns the length factor for the major tick marks.  The value is
 328:      * subtracted from the tick radius to determine the inner starting point
 329:      * for the tick marks.
 330:      *
 331:      * @return The length factor.
 332:      *
 333:      * @see #setMajorTickLength(double)
 334:      */
 335:     public double getMajorTickLength() {
 336:         return this.majorTickLength;
 337:     }
 338:     
 339:     /**
 340:      * Sets the length factor for the major tick marks and sends a
 341:      * {@link DialLayerChangeEvent} to all registered listeners.
 342:      *
 343:      * @param length  the length.
 344:      *
 345:      * @see #getMajorTickLength()
 346:      */
 347:     public void setMajorTickLength(double length) {
 348:         if (length < 0.0) {
 349:             throw new IllegalArgumentException("Negative 'length' argument.");
 350:         }
 351:         this.majorTickLength = length;
 352:         notifyListeners(new DialLayerChangeEvent(this));
 353:     }
 354:     
 355:     /**
 356:      * Returns the major tick paint.
 357:      *
 358:      * @return The major tick paint (never <code>null</code>).
 359:      *
 360:      * @see #setMajorTickPaint(Paint)
 361:      */
 362:     public Paint getMajorTickPaint() {
 363:         return this.majorTickPaint;
 364:     }
 365:     
 366:     /**
 367:      * Sets the major tick paint and sends a {@link DialLayerChangeEvent} to 
 368:      * all registered listeners.
 369:      *
 370:      * @param paint  the paint (<code>null</code> not permitted).
 371:      *
 372:      * @see #getMajorTickPaint()
 373:      */
 374:     public void setMajorTickPaint(Paint paint) {
 375:         if (paint == null) {
 376:             throw new IllegalArgumentException("Null 'paint' argument.");
 377:         }
 378:         this.majorTickPaint = paint;
 379:         notifyListeners(new DialLayerChangeEvent(this));
 380:     }
 381:     
 382:     /**
 383:      * Returns the stroke used to draw the major tick marks.
 384:      *
 385:      * @return The stroke (never <code>null</code>).
 386:      *
 387:      * @see #setMajorTickStroke(Stroke)
 388:      */
 389:     public Stroke getMajorTickStroke() {
 390:         return this.majorTickStroke;
 391:     }
 392:     
 393:     /**
 394:      * Sets the stroke used to draw the major tick marks and sends a 
 395:      * {@link DialLayerChangeEvent} to all registered listeners.
 396:      *
 397:      * @param stroke  the stroke (<code>null</code> not permitted).
 398:      *
 399:      * @see #getMajorTickStroke()
 400:      */
 401:     public void setMajorTickStroke(Stroke stroke) {
 402:         if (stroke == null) {
 403:             throw new IllegalArgumentException("Null 'stroke' argument.");
 404:         }
 405:         this.majorTickStroke = stroke;
 406:         notifyListeners(new DialLayerChangeEvent(this));
 407:     }
 408:     
 409:     /**
 410:      * Returns the number of minor tick marks between major tick marks.
 411:      *
 412:      * @return The number of minor tick marks between major tick marks.
 413:      *
 414:      * @see #setMinorTickCount(int)
 415:      */
 416:     public int getMinorTickCount() {
 417:         return this.minorTickCount;
 418:     }
 419:     
 420:     /**
 421:      * Sets the number of minor tick marks between major tick marks and sends 
 422:      * a {@link DialLayerChangeEvent} to all registered listeners.
 423:      *
 424:      * @param count  the count.
 425:      *
 426:      * @see #getMinorTickCount()
 427:      */
 428:     public void setMinorTickCount(int count) {
 429:         if (count < 0) {
 430:             throw new IllegalArgumentException(
 431:                     "The 'count' cannot be negative.");
 432:         }
 433:         this.minorTickCount = count;
 434:         notifyListeners(new DialLayerChangeEvent(this));
 435:     }
 436:     
 437:     /**
 438:      * Returns the length factor for the minor tick marks.  The value is
 439:      * subtracted from the tick radius to determine the inner starting point
 440:      * for the tick marks.
 441:      *
 442:      * @return The length factor.
 443:      *
 444:      * @see #setMinorTickLength(double)
 445:      */
 446:     public double getMinorTickLength() {
 447:         return this.minorTickLength;
 448:     }
 449:     
 450:     /**
 451:      * Sets the length factor for the minor tick marks and sends 
 452:      * a {@link DialLayerChangeEvent} to all registered listeners.
 453:      *
 454:      * @param length  the length.
 455:      *
 456:      * @see #getMinorTickLength()
 457:      */
 458:     public void setMinorTickLength(double length) {
 459:         if (length < 0.0) { 
 460:             throw new IllegalArgumentException("Negative 'length' argument.");
 461:         }
 462:         this.minorTickLength = length;
 463:         notifyListeners(new DialLayerChangeEvent(this));
 464:     }
 465:     
 466:     /**
 467:      * Returns the paint used to draw the minor tick marks.
 468:      * 
 469:      * @return The paint (never <code>null</code>).
 470:      * 
 471:      * @see #setMinorTickPaint(Paint)
 472:      */
 473:     public Paint getMinorTickPaint() {
 474:         return this.minorTickPaint;
 475:     }
 476:     
 477:     /**
 478:      * Sets the paint used to draw the minor tick marks and sends a 
 479:      * {@link DialLayerChangeEvent} to all registered listeners.
 480:      * 
 481:      * @param paint  the paint (<code>null</code> not permitted).
 482:      * 
 483:      * @see #getMinorTickPaint()
 484:      */
 485:     public void setMinorTickPaint(Paint paint) {
 486:         if (paint == null) {
 487:             throw new IllegalArgumentException("Null 'paint' argument.");
 488:         }
 489:         this.minorTickPaint = paint;
 490:         notifyListeners(new DialLayerChangeEvent(this));        
 491:     }
 492:     
 493:     /**
 494:      * Returns the tick label offset.
 495:      *
 496:      * @return The tick label offset.
 497:      *
 498:      * @see #setTickLabelOffset(double)
 499:      */
 500:     public double getTickLabelOffset() {
 501:         return this.tickLabelOffset;
 502:     }
 503:     
 504:     /**
 505:      * Sets the tick label offset and sends a {@link DialLayerChangeEvent} to 
 506:      * all registered listeners.
 507:      *
 508:      * @param offset  the offset.
 509:      *
 510:      * @see #getTickLabelOffset()
 511:      */
 512:     public void setTickLabelOffset(double offset) {
 513:         this.tickLabelOffset = offset;
 514:         notifyListeners(new DialLayerChangeEvent(this));
 515:     }
 516:     
 517:     /**
 518:      * Returns the font used to draw the tick labels.
 519:      *
 520:      * @return The font (never <code>null</code>).
 521:      *
 522:      * @see #setTickLabelFont(Font)
 523:      */
 524:     public Font getTickLabelFont() {
 525:         return this.tickLabelFont;
 526:     }
 527:     
 528:     /**
 529:      * Sets the font used to display the tick labels and sends a 
 530:      * {@link DialLayerChangeEvent} to all registered listeners.
 531:      *
 532:      * @param font  the font (<code>null</code> not permitted).
 533:      *
 534:      * @see #getTickLabelFont()
 535:      */
 536:     public void setTickLabelFont(Font font) {
 537:         if (font == null) {
 538:             throw new IllegalArgumentException("Null 'font' argument.");
 539:         }
 540:         this.tickLabelFont = font;
 541:         notifyListeners(new DialLayerChangeEvent(this));
 542:     }
 543:     
 544:     /**
 545:      * Returns the paint used to draw the tick labels.
 546:      *
 547:      * @return The paint (<code>null</code> not permitted).
 548:      * 
 549:      * @see #setTickLabelPaint(Paint)
 550:      */
 551:     public Paint getTickLabelPaint() {
 552:         return this.tickLabelPaint;
 553:     }
 554:     
 555:     /**
 556:      * Sets the paint used to draw the tick labels and sends a 
 557:      * {@link DialLayerChangeEvent} to all registered listeners.
 558:      *
 559:      * @param paint  the paint (<code>null</code> not permitted).
 560:      */
 561:     public void setTickLabelPaint(Paint paint) {
 562:         if (paint == null) {
 563:             throw new IllegalArgumentException("Null 'paint' argument.");
 564:         }
 565:         this.tickLabelPaint = paint;
 566:         notifyListeners(new DialLayerChangeEvent(this));
 567:     }
 568:     
 569:     /**
 570:      * Returns <code>true</code> if the tick labels should be displayed,
 571:      * and <code>false</code> otherwise.
 572:      * 
 573:      * @return A boolean.
 574:      * 
 575:      * @see #setTickLabelsVisible(boolean)
 576:      */
 577:     public boolean getTickLabelsVisible() {
 578:         return this.tickLabelsVisible;
 579:     }
 580:     
 581:     /**
 582:      * Sets the flag that controls whether or not the tick labels are
 583:      * displayed, and sends a {@link DialLayerChangeEvent} to all registered
 584:      * listeners.
 585:      * 
 586:      * @param visible  the new flag value.
 587:      * 
 588:      * @see #getTickLabelsVisible()
 589:      */
 590:     public void setTickLabelsVisible(boolean visible) {
 591:         this.tickLabelsVisible = visible;
 592:         notifyListeners(new DialLayerChangeEvent(this));
 593:     }
 594:     
 595:     /**
 596:      * Returns the number formatter used to convert the tick label values to
 597:      * strings.
 598:      * 
 599:      * @return The formatter (never <code>null</code>).
 600:      * 
 601:      * @see #setTickLabelFormatter(NumberFormat)
 602:      */
 603:     public NumberFormat getTickLabelFormatter() {
 604:         return this.tickLabelFormatter;
 605:     }
 606:     
 607:     /**
 608:      * Sets the number formatter used to convert the tick label values to 
 609:      * strings, and sends a {@link DialLayerChangeEvent} to all registered
 610:      * listeners.
 611:      * 
 612:      * @param formatter  the formatter (<code>null</code> not permitted).
 613:      * 
 614:      * @see #getTickLabelFormatter()
 615:      */
 616:     public void setTickLabelFormatter(NumberFormat formatter) {
 617:         if (formatter == null) {
 618:             throw new IllegalArgumentException("Null 'formatter' argument.");
 619:         }
 620:         this.tickLabelFormatter = formatter;
 621:         notifyListeners(new DialLayerChangeEvent(this));        
 622:     }
 623:     
 624:     /**
 625:      * Returns a flag that controls whether or not the first tick label is
 626:      * visible.
 627:      * 
 628:      * @return A boolean.
 629:      * 
 630:      * @see #setFirstTickLabelVisible(boolean)
 631:      */
 632:     public boolean getFirstTickLabelVisible() {
 633:         return this.firstTickLabelVisible;
 634:     }
 635:     
 636:     /**
 637:      * Sets a flag that controls whether or not the first tick label is 
 638:      * visible, and sends a {@link DialLayerChangeEvent} to all registered
 639:      * listeners.
 640:      * 
 641:      * @param visible  the new flag value.
 642:      * 
 643:      * @see #getFirstTickLabelVisible()
 644:      */
 645:     public void setFirstTickLabelVisible(boolean visible) {
 646:         this.firstTickLabelVisible = visible;
 647:         notifyListeners(new DialLayerChangeEvent(this));
 648:     }
 649:     
 650:     /**
 651:      * Returns <code>true</code> to indicate that this layer should be 
 652:      * clipped within the dial window. 
 653:      * 
 654:      * @return <code>true</code>.
 655:      */
 656:     public boolean isClippedToWindow() {
 657:         return true;
 658:     }
 659:     
 660:     /**
 661:      * Draws the scale on the dial plot.
 662:      *
 663:      * @param g2  the graphics target (<code>null</code> not permitted).
 664:      * @param plot  the dial plot (<code>null</code> not permitted).
 665:      * @param frame  the reference frame that is used to construct the
 666:      *     geometry of the plot (<code>null</code> not permitted).
 667:      * @param view  the visible part of the plot (<code>null</code> not 
 668:      *     permitted).
 669:      */
 670:     public void draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, 
 671:             Rectangle2D view) {
 672:         
 673:         Rectangle2D arcRect = DialPlot.rectangleByRadius(frame, 
 674:                 this.tickRadius, this.tickRadius);
 675:         Rectangle2D arcRectMajor = DialPlot.rectangleByRadius(frame, 
 676:                 this.tickRadius - this.majorTickLength, 
 677:                 this.tickRadius - this.majorTickLength);
 678:         Rectangle2D arcRectMinor = arcRect;
 679:         if (this.minorTickCount > 0 && this.minorTickLength > 0.0) {
 680:             arcRectMinor = DialPlot.rectangleByRadius(frame, 
 681:                     this.tickRadius - this.minorTickLength, 
 682:                     this.tickRadius - this.minorTickLength);
 683:         }
 684:         Rectangle2D arcRectForLabels = DialPlot.rectangleByRadius(frame, 
 685:                 this.tickRadius - this.tickLabelOffset, 
 686:                 this.tickRadius - this.tickLabelOffset);
 687:         
 688:         boolean firstLabel = true;
 689:         
 690:         Arc2D arc = new Arc2D.Double();
 691:         Line2D workingLine = new Line2D.Double();
 692:         for (double v = this.lowerBound; v <= this.upperBound; 
 693:                 v += this.majorTickIncrement) {
 694:             arc.setArc(arcRect, this.startAngle, valueToAngle(v) 
 695:                     - this.startAngle, Arc2D.OPEN);
 696:             Point2D pt0 = arc.getEndPoint();
 697:             arc.setArc(arcRectMajor, this.startAngle, valueToAngle(v) 
 698:                     - this.startAngle, Arc2D.OPEN);
 699:             Point2D pt1 = arc.getEndPoint();
 700:             g2.setPaint(this.majorTickPaint);
 701:             g2.setStroke(this.majorTickStroke);
 702:             workingLine.setLine(pt0, pt1);
 703:             g2.draw(workingLine);
 704:             arc.setArc(arcRectForLabels, this.startAngle, valueToAngle(v) 
 705:                     - this.startAngle, Arc2D.OPEN);
 706:             Point2D pt2 = arc.getEndPoint();
 707:             
 708:             if (this.tickLabelsVisible) {
 709:                 if (!firstLabel || this.firstTickLabelVisible) {
 710:                     g2.setFont(this.tickLabelFont);
 711:                     TextUtilities.drawAlignedString(
 712:                             this.tickLabelFormatter.format(v), g2, 
 713:                             (float) pt2.getX(), (float) pt2.getY(), 
 714:                             TextAnchor.CENTER);
 715:                 }
 716:             }
 717:             firstLabel = false;
 718:             
 719:             // now do the minor tick marks
 720:             if (this.minorTickCount > 0 && this.minorTickLength > 0.0) {
 721:                 double minorTickIncrement = this.majorTickIncrement 
 722:                         / (this.minorTickCount + 1);
 723:                 for (int i = 0; i < this.minorTickCount; i++) {
 724:                     double vv = v + ((i + 1) * minorTickIncrement);
 725:                     if (vv >= this.upperBound) {
 726:                         break;
 727:                     }
 728:                     double angle = valueToAngle(vv);
 729:                    
 730:                     arc.setArc(arcRect, this.startAngle, angle 
 731:                             - this.startAngle, Arc2D.OPEN);
 732:                     pt0 = arc.getEndPoint();
 733:                     arc.setArc(arcRectMinor, this.startAngle, angle 
 734:                             - this.startAngle, Arc2D.OPEN);
 735:                     Point2D pt3 = arc.getEndPoint();
 736:                     g2.setStroke(this.minorTickStroke);
 737:                     g2.setPaint(this.minorTickPaint);
 738:                     workingLine.setLine(pt0, pt3);
 739:                     g2.draw(workingLine);
 740:                 }
 741:             }
 742:             
 743:         }
 744:     }
 745:     
 746:     /**
 747:      * Converts a data value to an angle against this scale.
 748:      *
 749:      * @param value  the data value.
 750:      *
 751:      * @return The angle (in degrees, using the same specification as Java's
 752:      *     Arc2D class).
 753:      *     
 754:      * @see #angleToValue(double)
 755:      */
 756:     public double valueToAngle(double value) {
 757:         double range = this.upperBound - this.lowerBound;
 758:         double unit = this.extent / range;
 759:         return this.startAngle + unit * (value - this.lowerBound);        
 760:     }
 761: 
 762:     /** 
 763:      * Converts the given angle to a data value, based on this scale.
 764:      * 
 765:      * @param angle  the angle.
 766:      * 
 767:      * @return The data value.
 768:      * 
 769:      * @see #valueToAngle(double)
 770:      */
 771:     public double angleToValue(double angle) {
 772:         return Double.NaN;  // FIXME
 773:     }
 774: 
 775:     /**
 776:      * Tests this <code>StandardDialScale</code> for equality with an arbitrary
 777:      * object.
 778:      *
 779:      * @param obj  the object (<code>null</code> permitted).
 780:      *
 781:      * @return A boolean.
 782:      */
 783:     public boolean equals(Object obj) {
 784:         if (obj == this) {
 785:             return true;
 786:         }    
 787:         if (!(obj instanceof StandardDialScale)) {
 788:             return false;
 789:         }
 790:         StandardDialScale that = (StandardDialScale) obj;
 791:         if (this.lowerBound != that.lowerBound) {
 792:             return false;
 793:         }
 794:         if (this.upperBound != that.upperBound) {
 795:             return false;
 796:         }
 797:         if (this.startAngle != that.startAngle) {
 798:             return false;
 799:         }
 800:         if (this.extent != that.extent) {
 801:             return false;
 802:         }
 803:         if (this.tickRadius != that.tickRadius) {
 804:             return false;
 805:         }
 806:         if (this.majorTickIncrement != that.majorTickIncrement) {
 807:             return false;
 808:         }
 809:         if (this.majorTickLength != that.majorTickLength) {
 810:             return false;
 811:         }
 812:         if (!PaintUtilities.equal(this.majorTickPaint, that.majorTickPaint)) {
 813:             return false;
 814:         }
 815:         if (!this.majorTickStroke.equals(that.majorTickStroke)) {
 816:             return false;
 817:         }
 818:         if (this.minorTickCount != that.minorTickCount) {
 819:             return false;
 820:         }
 821:         if (this.minorTickLength != that.minorTickLength) {
 822:             return false;
 823:         }
 824:         if (!PaintUtilities.equal(this.minorTickPaint, that.minorTickPaint)) {
 825:             return false;
 826:         }
 827:         if (!this.minorTickStroke.equals(that.minorTickStroke)) {
 828:             return false;
 829:         }
 830:         if (this.tickLabelsVisible != that.tickLabelsVisible) {
 831:             return false;
 832:         }
 833:         if (this.tickLabelOffset != that.tickLabelOffset) {
 834:             return false;
 835:         }
 836:         if (!this.tickLabelFont.equals(that.tickLabelFont)) {
 837:             return false;
 838:         }
 839:         if (!PaintUtilities.equal(this.tickLabelPaint, that.tickLabelPaint)) {
 840:             return false;
 841:         }
 842:         return super.equals(obj);
 843:     }
 844:     
 845:     /**
 846:      * Returns a hash code for this instance.
 847:      * 
 848:      * @return A hash code.
 849:      */
 850:     public int hashCode() {
 851:         int result = 193;
 852:         // lowerBound
 853:         long temp = Double.doubleToLongBits(this.lowerBound);
 854:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 855:         // upperBound
 856:         temp = Double.doubleToLongBits(this.upperBound);
 857:         result = 37 * result + (int) (temp ^ (temp >>> 32));
 858:         // startAngle
 859:         temp = Double.doubleToLongBits(this.startAngle);
 860:         result = 37 * result + (int) (temp ^ (temp >>> 32));        
 861:         // extent
 862:         temp = Double.doubleToLongBits(this.extent);
 863:         result = 37 * result + (int) (temp ^ (temp >>> 32));        
 864:         // tickRadius
 865:         temp = Double.doubleToLongBits(this.tickRadius);
 866:         result = 37 * result + (int) (temp ^ (temp >>> 32));        
 867:         // majorTickIncrement
 868:         // majorTickLength
 869:         // majorTickPaint
 870:         // majorTickStroke
 871:         // minorTickCount
 872:         // minorTickLength
 873:         // minorTickPaint
 874:         // minorTickStroke
 875:         // tickLabelOffset
 876:         // tickLabelFont
 877:         // tickLabelsVisible
 878:         // tickLabelFormatter
 879:         // firstTickLabelsVisible
 880:         return result; 
 881:     }
 882: 
 883:     /**
 884:      * Returns a clone of this instance.
 885:      * 
 886:      * @return A clone.
 887:      * 
 888:      * @throws CloneNotSupportedException if this instance is not cloneable.
 889:      */
 890:     public Object clone() throws CloneNotSupportedException { 
 891:         return super.clone();
 892:     }
 893:     
 894:     /**
 895:      * Provides serialization support.
 896:      *
 897:      * @param stream  the output stream.
 898:      *
 899:      * @throws IOException  if there is an I/O error.
 900:      */
 901:     private void writeObject(ObjectOutputStream stream) throws IOException {
 902:         stream.defaultWriteObject();
 903:         SerialUtilities.writePaint(this.majorTickPaint, stream);
 904:         SerialUtilities.writeStroke(this.majorTickStroke, stream);
 905:         SerialUtilities.writePaint(this.minorTickPaint, stream);
 906:         SerialUtilities.writeStroke(this.minorTickStroke, stream);
 907:         SerialUtilities.writePaint(this.tickLabelPaint, stream);
 908:     }
 909: 
 910:     /**
 911:      * Provides serialization support.
 912:      *
 913:      * @param stream  the input stream.
 914:      *
 915:      * @throws IOException  if there is an I/O error.
 916:      * @throws ClassNotFoundException  if there is a classpath problem.
 917:      */
 918:     private void readObject(ObjectInputStream stream) 
 919:             throws IOException, ClassNotFoundException {
 920:         stream.defaultReadObject();
 921:         this.majorTickPaint = SerialUtilities.readPaint(stream);
 922:         this.majorTickStroke = SerialUtilities.readStroke(stream);
 923:         this.minorTickPaint = SerialUtilities.readPaint(stream);
 924:         this.minorTickStroke = SerialUtilities.readStroke(stream);
 925:         this.tickLabelPaint = SerialUtilities.readPaint(stream);
 926:     }
 927: 
 928: }