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