1 /* ===========================================================
2  * JFreeChart : a free chart library for the Java(tm) platform
3  * ===========================================================
4  *
5  * (C) Copyright 2000-2013, by Object Refinery Limited and Contributors.
6  *
7  * Project Info:  http://www.jfree.org/jfreechart/index.html
8  *
9  * This library is free software; you can redistribute it and/or modify it
10  * under the terms of the GNU Lesser General Public License as published by
11  * the Free Software Foundation; either version 2.1 of the License, or
12  * (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17  * License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
22  * USA.
23  *
24  * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
25  * Other names may be trademarks of their respective owners.]
26  *
27  * ----------------------
28  * StandardDialScale.java
29  * ----------------------
30  * (C) Copyright 2006-2013, by Object Refinery Limited.
31  *
32  * Original Author:  David Gilbert (for Object Refinery Limited);
33  * Contributor(s):   -;
34  *
35  * Changes
36  * -------
37  * 03-Nov-2006 : Version 1 (DG);
38  * 17-Nov-2006 : Added flags for tick label visibility (DG);
39  * 24-Oct-2007 : Added tick label formatter (DG);
40  * 19-Nov-2007 : Added some missing accessor methods (DG);
41  * 27-Feb-2009 : Fixed bug 2617557: tickLabelPaint ignored (DG);
42  * 09-Feb-2010 : Fixed bug 2946521 (DG);
43  * 08-Jan-2012 : Added missing angleToValue() implementation (DG);
44  * 03-Jul-2013 : Use ParamChecks (DG);
45  *
46  */
47 
48 package org.jfree.chart.plot.dial;
49 
50 import java.awt.BasicStroke;
51 import java.awt.Color;
52 import java.awt.Font;
53 import java.awt.Graphics2D;
54 import java.awt.Paint;
55 import java.awt.Stroke;
56 import java.awt.geom.Arc2D;
57 import java.awt.geom.Line2D;
58 import java.awt.geom.Point2D;
59 import java.awt.geom.Rectangle2D;
60 import java.io.IOException;
61 import java.io.ObjectInputStream;
62 import java.io.ObjectOutputStream;
63 import java.io.Serializable;
64 import java.text.DecimalFormat;
65 import java.text.NumberFormat;
66 import org.jfree.chart.util.ParamChecks;
67 
68 import org.jfree.io.SerialUtilities;
69 import org.jfree.text.TextUtilities;
70 import org.jfree.ui.TextAnchor;
71 import org.jfree.util.PaintUtilities;
72 import org.jfree.util.PublicCloneable;
73 
74 /**
75  * A scale for a {@link DialPlot}.
76  *
77  * @since 1.0.7
78  */
79 public class StandardDialScale extends AbstractDialLayer implements DialScale,
80         Cloneable, PublicCloneable, Serializable {
81 
82     /** For serialization. */
83     static final long serialVersionUID = 3715644629665918516L;
84 
85     /** The minimum data value for the scale. */
86     private double lowerBound;
87 
88     /** The maximum data value for the scale. */
89     private double upperBound;
90 
91     /**
92      * The start angle for the scale display, in degrees (using the same
93      * encoding as Arc2D).
94      */
95     private double startAngle;
96 
97     /** The extent of the scale display. */
98     private double extent;
99 
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      */
StandardDialScale()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 > 0).
202      * @param minorTickCount  the number of minor ticks between major tick
203      *          marks.
204      */
StandardDialScale(double lowerBound, double upperBound, double startAngle, double extent, double majorTickIncrement, int minorTickCount)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      */
getLowerBound()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      */
setLowerBound(double lower)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      */
getUpperBound()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      */
setUpperBound(double upper)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      */
getStartAngle()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      */
setStartAngle(double angle)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      */
getExtent()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      */
setExtent(double extent)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      */
getTickRadius()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      */
setTickRadius(double radius)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      */
getMajorTickIncrement()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 > 0).
383      *
384      * @see #getMajorTickIncrement()
385      */
setMajorTickIncrement(double increment)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      */
getMajorTickLength()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      */
setMajorTickLength(double length)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      */
getMajorTickPaint()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      */
setMajorTickPaint(Paint paint)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      */
getMajorTickStroke()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      */
setMajorTickStroke(Stroke stroke)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      */
getMinorTickCount()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      */
setMinorTickCount(int count)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      */
getMinorTickLength()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      */
setMinorTickLength(double length)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      */
getMinorTickPaint()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      */
setMinorTickPaint(Paint paint)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      */
getMinorTickStroke()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      */
setMinorTickStroke(Stroke stroke)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      */
getTickLabelOffset()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      */
setTickLabelOffset(double offset)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      */
getTickLabelFont()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      */
setTickLabelFont(Font font)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      */
getTickLabelPaint()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      */
setTickLabelPaint(Paint paint)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      */
getTickLabelsVisible()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      */
setTickLabelsVisible(boolean visible)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      */
getTickLabelFormatter()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      */
setTickLabelFormatter(NumberFormat formatter)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      */
getFirstTickLabelVisible()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      */
setFirstTickLabelVisible(boolean visible)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
isClippedToWindow()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
draw(Graphics2D g2, DialPlot plot, Rectangle2D frame, Rectangle2D view)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
valueToAngle(double value)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
angleToValue(double angle)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
equals(Object obj)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
hashCode()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
clone()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      */
writeObject(ObjectOutputStream stream)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      */
readObject(ObjectInputStream stream)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 }
1025