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