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