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