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    }