    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
    046    package org.jfree.chart.plot.dial;
    047
    048    import java.awt.BasicStroke;
    049    import java.awt.Color;
    050    import java.awt.Font;
    051    import java.awt.Graphics2D;
    052    import java.awt.Paint;
    053    import java.awt.Stroke;
    054    import java.awt.geom.Arc2D;
    055    import java.awt.geom.Line2D;
    056    import java.awt.geom.Point2D;
    057    import java.awt.geom.Rectangle2D;
    058    import java.io.IOException;
    059    import java.io.ObjectInputStream;
    060    import java.io.ObjectOutputStream;
    061    import java.io.Serializable;
    062    import java.text.DecimalFormat;
    063    import java.text.NumberFormat;
    064
    065    import org.jfree.io.SerialUtilities;
    066    import org.jfree.text.TextUtilities;
    067    import org.jfree.ui.TextAnchor;
    068    import org.jfree.util.PaintUtilities;
    069    import org.jfree.util.PublicCloneable;
    070
    071    /**
    072     * A scale for a {@link DialPlot}.
    073     *
    074     * @since 1.0.7
    075     */
    076    public 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    }