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