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