001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-present, by David Gilbert and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jfreechart/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 
025 * Other names may be trademarks of their respective owners.]
026 *
027 * ---------------------
028 * PaintScaleLegend.java
029 * ---------------------
030 * (C) Copyright 2007-present, by David Gilbert.
031 *
032 * Original Author:  David Gilbert;
033 * Contributor(s):   Peter Kolb - see patch 2686872;
034 *                   Tracy Hiltbrand (equals/hashCode comply with EqualsVerifier);
035 *
036 */
037
038package org.jfree.chart.title;
039
040import java.awt.BasicStroke;
041import java.awt.Color;
042import java.awt.Graphics2D;
043import java.awt.Paint;
044import java.awt.Stroke;
045import java.awt.geom.Rectangle2D;
046import java.io.IOException;
047import java.io.ObjectInputStream;
048import java.io.ObjectOutputStream;
049import java.util.Objects;
050import org.jfree.chart.HashUtils;
051
052import org.jfree.chart.axis.AxisLocation;
053import org.jfree.chart.axis.AxisSpace;
054import org.jfree.chart.axis.ValueAxis;
055import org.jfree.chart.block.LengthConstraintType;
056import org.jfree.chart.block.RectangleConstraint;
057import org.jfree.chart.event.AxisChangeEvent;
058import org.jfree.chart.event.AxisChangeListener;
059import org.jfree.chart.event.TitleChangeEvent;
060import org.jfree.chart.plot.Plot;
061import org.jfree.chart.plot.PlotOrientation;
062import org.jfree.chart.renderer.PaintScale;
063import org.jfree.chart.ui.RectangleEdge;
064import org.jfree.chart.ui.Size2D;
065import org.jfree.chart.util.PaintUtils;
066import org.jfree.chart.util.Args;
067import org.jfree.chart.util.PublicCloneable;
068import org.jfree.chart.util.SerialUtils;
069import org.jfree.data.Range;
070
071/**
072 * A legend that shows a range of values and their associated colors, driven
073 * by an underlying {@link PaintScale} implementation.
074 */
075public class PaintScaleLegend extends Title implements AxisChangeListener,
076        PublicCloneable {
077
078    /** For serialization. */
079    static final long serialVersionUID = -1365146490993227503L;
080
081    /** The paint scale (never {@code null}). */
082    private PaintScale scale;
083
084    /** The value axis (never {@code null}). */
085    private ValueAxis axis;
086
087    /**
088     * The axis location (handles both orientations, never
089     * {@code null}).
090     */
091    private AxisLocation axisLocation;
092
093    /** The offset between the axis and the paint strip (in Java2D units). */
094    private double axisOffset;
095
096    /** The thickness of the paint strip (in Java2D units). */
097    private double stripWidth;
098
099    /**
100     * A flag that controls whether or not an outline is drawn around the
101     * paint strip.
102     */
103    private boolean stripOutlineVisible;
104
105    /** The paint used to draw an outline around the paint strip. */
106    private transient Paint stripOutlinePaint;
107
108    /** The stroke used to draw an outline around the paint strip. */
109    private transient Stroke stripOutlineStroke;
110
111    /** The background paint (never {@code null}). */
112    private transient Paint backgroundPaint;
113
114    /**
115     * The number of subdivisions for the scale when rendering.
116     */
117    private int subdivisions;
118
119    /**
120     * Creates a new instance.
121     *
122     * @param scale  the scale ({@code null} not permitted).
123     * @param axis  the axis ({@code null} not permitted).
124     */
125    public PaintScaleLegend(PaintScale scale, ValueAxis axis) {
126        Args.nullNotPermitted(axis, "axis");
127        this.scale = scale;
128        this.axis = axis;
129        this.axis.addChangeListener(this);
130        this.axisLocation = AxisLocation.BOTTOM_OR_LEFT;
131        this.axisOffset = 0.0;
132        this.axis.setRange(scale.getLowerBound(), scale.getUpperBound());
133        this.stripWidth = 15.0;
134        this.stripOutlineVisible = true;
135        this.stripOutlinePaint = Color.GRAY;
136        this.stripOutlineStroke = new BasicStroke(0.5f);
137        this.backgroundPaint = Color.WHITE;
138        this.subdivisions = 100;
139    }
140
141    /**
142     * Returns the scale used to convert values to colors.
143     *
144     * @return The scale (never {@code null}).
145     *
146     * @see #setScale(PaintScale)
147     */
148    public PaintScale getScale() {
149        return this.scale;
150    }
151
152    /**
153     * Sets the scale and sends a {@link TitleChangeEvent} to all registered
154     * listeners.
155     *
156     * @param scale  the scale ({@code null} not permitted).
157     *
158     * @see #getScale()
159     */
160    public void setScale(PaintScale scale) {
161        Args.nullNotPermitted(scale, "scale");
162        this.scale = scale;
163        notifyListeners(new TitleChangeEvent(this));
164    }
165
166    /**
167     * Returns the axis for the paint scale.
168     *
169     * @return The axis (never {@code null}).
170     *
171     * @see #setAxis(ValueAxis)
172     */
173    public ValueAxis getAxis() {
174        return this.axis;
175    }
176
177    /**
178     * Sets the axis for the paint scale and sends a {@link TitleChangeEvent}
179     * to all registered listeners.
180     *
181     * @param axis  the axis ({@code null} not permitted).
182     *
183     * @see #getAxis()
184     */
185    public void setAxis(ValueAxis axis) {
186        Args.nullNotPermitted(axis, "axis");
187        this.axis.removeChangeListener(this);
188        this.axis = axis;
189        this.axis.addChangeListener(this);
190        notifyListeners(new TitleChangeEvent(this));
191    }
192
193    /**
194     * Returns the axis location.
195     *
196     * @return The axis location (never {@code null}).
197     *
198     * @see #setAxisLocation(AxisLocation)
199     */
200    public AxisLocation getAxisLocation() {
201        return this.axisLocation;
202    }
203
204    /**
205     * Sets the axis location and sends a {@link TitleChangeEvent} to all
206     * registered listeners.
207     *
208     * @param location  the location ({@code null} not permitted).
209     *
210     * @see #getAxisLocation()
211     */
212    public void setAxisLocation(AxisLocation location) {
213        Args.nullNotPermitted(location, "location");
214        this.axisLocation = location;
215        notifyListeners(new TitleChangeEvent(this));
216    }
217
218    /**
219     * Returns the offset between the axis and the paint strip.
220     *
221     * @return The offset between the axis and the paint strip.
222     *
223     * @see #setAxisOffset(double)
224     */
225    public double getAxisOffset() {
226        return this.axisOffset;
227    }
228
229    /**
230     * Sets the offset between the axis and the paint strip and sends a
231     * {@link TitleChangeEvent} to all registered listeners.
232     *
233     * @param offset  the offset.
234     */
235    public void setAxisOffset(double offset) {
236        this.axisOffset = offset;
237        notifyListeners(new TitleChangeEvent(this));
238    }
239
240    /**
241     * Returns the width of the paint strip, in Java2D units.
242     *
243     * @return The width of the paint strip.
244     *
245     * @see #setStripWidth(double)
246     */
247    public double getStripWidth() {
248        return this.stripWidth;
249    }
250
251    /**
252     * Sets the width of the paint strip and sends a {@link TitleChangeEvent}
253     * to all registered listeners.
254     *
255     * @param width  the width.
256     *
257     * @see #getStripWidth()
258     */
259    public void setStripWidth(double width) {
260        this.stripWidth = width;
261        notifyListeners(new TitleChangeEvent(this));
262    }
263
264    /**
265     * Returns the flag that controls whether or not an outline is drawn
266     * around the paint strip.
267     *
268     * @return A boolean.
269     *
270     * @see #setStripOutlineVisible(boolean)
271     */
272    public boolean isStripOutlineVisible() {
273        return this.stripOutlineVisible;
274    }
275
276    /**
277     * Sets the flag that controls whether or not an outline is drawn around
278     * the paint strip, and sends a {@link TitleChangeEvent} to all registered
279     * listeners.
280     *
281     * @param visible  the flag.
282     *
283     * @see #isStripOutlineVisible()
284     */
285    public void setStripOutlineVisible(boolean visible) {
286        this.stripOutlineVisible = visible;
287        notifyListeners(new TitleChangeEvent(this));
288    }
289
290    /**
291     * Returns the paint used to draw the outline of the paint strip.
292     *
293     * @return The paint (never {@code null}).
294     *
295     * @see #setStripOutlinePaint(Paint)
296     */
297    public Paint getStripOutlinePaint() {
298        return this.stripOutlinePaint;
299    }
300
301    /**
302     * Sets the paint used to draw the outline of the paint strip, and sends
303     * a {@link TitleChangeEvent} to all registered listeners.
304     *
305     * @param paint  the paint ({@code null} not permitted).
306     *
307     * @see #getStripOutlinePaint()
308     */
309    public void setStripOutlinePaint(Paint paint) {
310        Args.nullNotPermitted(paint, "paint");
311        this.stripOutlinePaint = paint;
312        notifyListeners(new TitleChangeEvent(this));
313    }
314
315    /**
316     * Returns the stroke used to draw the outline around the paint strip.
317     *
318     * @return The stroke (never {@code null}).
319     *
320     * @see #setStripOutlineStroke(Stroke)
321     */
322    public Stroke getStripOutlineStroke() {
323        return this.stripOutlineStroke;
324    }
325
326    /**
327     * Sets the stroke used to draw the outline around the paint strip and
328     * sends a {@link TitleChangeEvent} to all registered listeners.
329     *
330     * @param stroke  the stroke ({@code null} not permitted).
331     *
332     * @see #getStripOutlineStroke()
333     */
334    public void setStripOutlineStroke(Stroke stroke) {
335        Args.nullNotPermitted(stroke, "stroke");
336        this.stripOutlineStroke = stroke;
337        notifyListeners(new TitleChangeEvent(this));
338    }
339
340    /**
341     * Returns the background paint.
342     *
343     * @return The background paint.
344     */
345    public Paint getBackgroundPaint() {
346        return this.backgroundPaint;
347    }
348
349    /**
350     * Sets the background paint and sends a {@link TitleChangeEvent} to all
351     * registered listeners.
352     *
353     * @param paint  the paint ({@code null} permitted).
354     */
355    public void setBackgroundPaint(Paint paint) {
356        this.backgroundPaint = paint;
357        notifyListeners(new TitleChangeEvent(this));
358    }
359
360    /**
361     * Returns the number of subdivisions used to draw the scale.
362     *
363     * @return The subdivision count.
364     */
365    public int getSubdivisionCount() {
366        return this.subdivisions;
367    }
368
369    /**
370     * Sets the subdivision count and sends a {@link TitleChangeEvent} to
371     * all registered listeners.
372     *
373     * @param count  the count.
374     */
375    public void setSubdivisionCount(int count) {
376        if (count <= 0) {
377            throw new IllegalArgumentException("Requires 'count' > 0.");
378        }
379        this.subdivisions = count;
380        notifyListeners(new TitleChangeEvent(this));
381    }
382
383    /**
384     * Receives notification of an axis change event and responds by firing
385     * a title change event.
386     *
387     * @param event  the event.
388     */
389    @Override
390    public void axisChanged(AxisChangeEvent event) {
391        if (this.axis == event.getAxis()) {
392            notifyListeners(new TitleChangeEvent(this));
393        }
394    }
395
396    /**
397     * Arranges the contents of the block, within the given constraints, and
398     * returns the block size.
399     *
400     * @param g2  the graphics device.
401     * @param constraint  the constraint ({@code null} not permitted).
402     *
403     * @return The block size (in Java2D units, never {@code null}).
404     */
405    @Override
406    public Size2D arrange(Graphics2D g2, RectangleConstraint constraint) {
407        RectangleConstraint cc = toContentConstraint(constraint);
408        LengthConstraintType w = cc.getWidthConstraintType();
409        LengthConstraintType h = cc.getHeightConstraintType();
410        Size2D contentSize = null;
411        if (w == LengthConstraintType.NONE) {
412            if (h == LengthConstraintType.NONE) {
413                contentSize = new Size2D(getWidth(), getHeight());
414            }
415            else if (h == LengthConstraintType.RANGE) {
416                throw new RuntimeException("Not yet implemented.");
417            }
418            else if (h == LengthConstraintType.FIXED) {
419                throw new RuntimeException("Not yet implemented.");
420            }
421        }
422        else if (w == LengthConstraintType.RANGE) {
423            if (h == LengthConstraintType.NONE) {
424                throw new RuntimeException("Not yet implemented.");
425            }
426            else if (h == LengthConstraintType.RANGE) {
427                contentSize = arrangeRR(g2, cc.getWidthRange(),
428                        cc.getHeightRange());
429            }
430            else if (h == LengthConstraintType.FIXED) {
431                throw new RuntimeException("Not yet implemented.");
432            }
433        }
434        else if (w == LengthConstraintType.FIXED) {
435            if (h == LengthConstraintType.NONE) {
436                throw new RuntimeException("Not yet implemented.");
437            }
438            else if (h == LengthConstraintType.RANGE) {
439                throw new RuntimeException("Not yet implemented.");
440            }
441            else if (h == LengthConstraintType.FIXED) {
442                throw new RuntimeException("Not yet implemented.");
443            }
444        }
445        assert contentSize != null; // suppress compiler warning
446        return new Size2D(calculateTotalWidth(contentSize.getWidth()),
447                calculateTotalHeight(contentSize.getHeight()));
448    }
449
450    /**
451     * Returns the content size for the title.  This will reflect the fact that
452     * a text title positioned on the left or right of a chart will be rotated
453     * 90 degrees.
454     *
455     * @param g2  the graphics device.
456     * @param widthRange  the width range.
457     * @param heightRange  the height range.
458     *
459     * @return The content size.
460     */
461    protected Size2D arrangeRR(Graphics2D g2, Range widthRange,
462            Range heightRange) {
463
464        RectangleEdge position = getPosition();
465        if (position == RectangleEdge.TOP || position == RectangleEdge.BOTTOM) {
466
467
468            float maxWidth = (float) widthRange.getUpperBound();
469
470            // determine the space required for the axis
471            AxisSpace space = this.axis.reserveSpace(g2, null,
472                    new Rectangle2D.Double(0, 0, maxWidth, 100),
473                    RectangleEdge.BOTTOM, null);
474
475            return new Size2D(maxWidth, this.stripWidth + this.axisOffset
476                    + space.getTop() + space.getBottom());
477        }
478        else if (position == RectangleEdge.LEFT || position
479                == RectangleEdge.RIGHT) {
480            float maxHeight = (float) heightRange.getUpperBound();
481            AxisSpace space = this.axis.reserveSpace(g2, null,
482                    new Rectangle2D.Double(0, 0, 100, maxHeight),
483                    RectangleEdge.RIGHT, null);
484            return new Size2D(this.stripWidth + this.axisOffset
485                    + space.getLeft() + space.getRight(), maxHeight);
486        }
487        else {
488            throw new RuntimeException("Unrecognised position.");
489        }
490    }
491
492    /**
493     * Draws the legend within the specified area.
494     *
495     * @param g2  the graphics target ({@code null} not permitted).
496     * @param area  the drawing area ({@code null} not permitted).
497     */
498    @Override
499    public void draw(Graphics2D g2, Rectangle2D area) {
500        draw(g2, area, null);
501    }
502
503    /**
504     * Draws the legend within the specified area.
505     *
506     * @param g2  the graphics target ({@code null} not permitted).
507     * @param area  the drawing area ({@code null} not permitted).
508     * @param params  drawing parameters (ignored here).
509     *
510     * @return {@code null}.
511     */
512    @Override
513    public Object draw(Graphics2D g2, Rectangle2D area, Object params) {
514        Rectangle2D target = (Rectangle2D) area.clone();
515        target = trimMargin(target);
516        if (this.backgroundPaint != null) {
517            g2.setPaint(this.backgroundPaint);
518            g2.fill(target);
519        }
520        getFrame().draw(g2, target);
521        getFrame().getInsets().trim(target);
522        target = trimPadding(target);
523        double base = this.axis.getLowerBound();
524        double increment = this.axis.getRange().getLength() / this.subdivisions;
525        Rectangle2D r = new Rectangle2D.Double();
526
527        if (RectangleEdge.isTopOrBottom(getPosition())) {
528            RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
529                    this.axisLocation, PlotOrientation.HORIZONTAL);
530            if (axisEdge == RectangleEdge.TOP) {
531                for (int i = 0; i < this.subdivisions; i++) {
532                    double v = base + (i * increment);
533                    Paint p = this.scale.getPaint(v);
534                    double vv0 = this.axis.valueToJava2D(v, target,
535                            RectangleEdge.TOP);
536                    double vv1 = this.axis.valueToJava2D(v + increment, target,
537                            RectangleEdge.TOP);
538                    double ww = Math.abs(vv1 - vv0) + 1.0;
539                    r.setRect(Math.min(vv0, vv1), target.getMaxY()
540                            - this.stripWidth, ww, this.stripWidth);
541                    g2.setPaint(p);
542                    g2.fill(r);
543                }
544                if (isStripOutlineVisible()) {
545                    g2.setPaint(this.stripOutlinePaint);
546                    g2.setStroke(this.stripOutlineStroke);
547                    g2.draw(new Rectangle2D.Double(target.getMinX(),
548                            target.getMaxY() - this.stripWidth,
549                            target.getWidth(), this.stripWidth));
550                }
551                this.axis.draw(g2, target.getMaxY() - this.stripWidth
552                        - this.axisOffset, target, target, RectangleEdge.TOP,
553                        null);
554            }
555            else if (axisEdge == RectangleEdge.BOTTOM) {
556                for (int i = 0; i < this.subdivisions; i++) {
557                    double v = base + (i * increment);
558                    Paint p = this.scale.getPaint(v);
559                    double vv0 = this.axis.valueToJava2D(v, target,
560                            RectangleEdge.BOTTOM);
561                    double vv1 = this.axis.valueToJava2D(v + increment, target,
562                            RectangleEdge.BOTTOM);
563                    double ww = Math.abs(vv1 - vv0) + 1.0;
564                    r.setRect(Math.min(vv0, vv1), target.getMinY(), ww,
565                            this.stripWidth);
566                    g2.setPaint(p);
567                    g2.fill(r);
568                }
569                if (isStripOutlineVisible()) {
570                    g2.setPaint(this.stripOutlinePaint);
571                    g2.setStroke(this.stripOutlineStroke);
572                    g2.draw(new Rectangle2D.Double(target.getMinX(),
573                            target.getMinY(), target.getWidth(),
574                            this.stripWidth));
575                }
576                this.axis.draw(g2, target.getMinY() + this.stripWidth
577                        + this.axisOffset, target, target,
578                        RectangleEdge.BOTTOM, null);
579            }
580        }
581        else {
582            RectangleEdge axisEdge = Plot.resolveRangeAxisLocation(
583                    this.axisLocation, PlotOrientation.VERTICAL);
584            if (axisEdge == RectangleEdge.LEFT) {
585                for (int i = 0; i < this.subdivisions; i++) {
586                    double v = base + (i * increment);
587                    Paint p = this.scale.getPaint(v);
588                    double vv0 = this.axis.valueToJava2D(v, target,
589                            RectangleEdge.LEFT);
590                    double vv1 = this.axis.valueToJava2D(v + increment, target,
591                            RectangleEdge.LEFT);
592                    double hh = Math.abs(vv1 - vv0) + 1.0;
593                    r.setRect(target.getMaxX() - this.stripWidth,
594                            Math.min(vv0, vv1), this.stripWidth, hh);
595                    g2.setPaint(p);
596                    g2.fill(r);
597                }
598                if (isStripOutlineVisible()) {
599                    g2.setPaint(this.stripOutlinePaint);
600                    g2.setStroke(this.stripOutlineStroke);
601                    g2.draw(new Rectangle2D.Double(target.getMaxX()
602                            - this.stripWidth, target.getMinY(), 
603                            this.stripWidth, target.getHeight()));
604                }
605                this.axis.draw(g2, target.getMaxX() - this.stripWidth
606                        - this.axisOffset, target, target, RectangleEdge.LEFT,
607                        null);
608            }
609            else if (axisEdge == RectangleEdge.RIGHT) {
610                for (int i = 0; i < this.subdivisions; i++) {
611                    double v = base + (i * increment);
612                    Paint p = this.scale.getPaint(v);
613                    double vv0 = this.axis.valueToJava2D(v, target,
614                            RectangleEdge.LEFT);
615                    double vv1 = this.axis.valueToJava2D(v + increment, target,
616                            RectangleEdge.LEFT);
617                    double hh = Math.abs(vv1 - vv0) + 1.0;
618                    r.setRect(target.getMinX(), Math.min(vv0, vv1),
619                            this.stripWidth, hh);
620                    g2.setPaint(p);
621                    g2.fill(r);
622                }
623                if (isStripOutlineVisible()) {
624                    g2.setPaint(this.stripOutlinePaint);
625                    g2.setStroke(this.stripOutlineStroke);
626                    g2.draw(new Rectangle2D.Double(target.getMinX(),
627                            target.getMinY(), this.stripWidth,
628                            target.getHeight()));
629                }
630                this.axis.draw(g2, target.getMinX() + this.stripWidth
631                        + this.axisOffset, target, target, RectangleEdge.RIGHT,
632                        null);
633            }
634        }
635        return null;
636    }
637
638    /**
639     * Tests this legend for equality with an arbitrary object.
640     *
641     * @param obj  the object ({@code null} permitted).
642     *
643     * @return A boolean.
644     */
645    @Override
646    public boolean equals(Object obj) {
647        if (obj == this) {
648            return true;
649        }
650        if (!(obj instanceof PaintScaleLegend)) {
651            return false;
652        }
653        PaintScaleLegend that = (PaintScaleLegend) obj;
654        if (!Objects.equals(this.scale, that.scale)) {
655            return false;
656        }
657        if (!Objects.equals(this.axis, that.axis)) {
658            return false;
659        }
660        if (!Objects.equals(this.axisLocation, that.axisLocation)) {
661            return false;
662        }
663        if (Double.doubleToLongBits(this.axisOffset) !=
664            Double.doubleToLongBits(that.axisOffset)) {
665            return false;
666        }
667        if (Double.doubleToLongBits(this.stripWidth) != 
668            Double.doubleToLongBits(that.stripWidth)) {
669            return false;
670        }
671        if (this.stripOutlineVisible != that.stripOutlineVisible) {
672            return false;
673        }
674        if (!PaintUtils.equal(this.stripOutlinePaint,
675                              that.stripOutlinePaint)) {
676            return false;
677        }
678        if (!Objects.equals(this.stripOutlineStroke, that.stripOutlineStroke)) {
679            return false;
680        }
681        if (!PaintUtils.equal(this.backgroundPaint, that.backgroundPaint)) {
682            return false;
683        }
684        if (this.subdivisions != that.subdivisions) {
685            return false;
686        }
687        if (!that.canEqual(this)) {
688            return false;
689        }
690        return super.equals(obj);
691    }
692
693    /**
694     * Ensures symmetry between super/subclass implementations of equals. For
695     * more detail, see http://jqno.nl/equalsverifier/manual/inheritance.
696     *
697     * @param other Object
698     * 
699     * @return true ONLY if the parameter is THIS class type
700     */
701    @Override
702    public boolean canEqual(Object other) {
703        // fix the "equals not symmetric" problem
704        return (other instanceof PaintScaleLegend);
705    }
706
707    @Override
708    public int hashCode() {
709        int hash = super.hashCode(); // equals calls superclass, hashCode must also
710        hash = 53 * hash + Objects.hashCode(this.scale);
711        hash = 53 * hash + Objects.hashCode(this.axis);
712        hash = 53 * hash + Objects.hashCode(this.axisLocation);
713        hash = 53 * hash + (int) (Double.doubleToLongBits(this.axisOffset) ^
714                      (Double.doubleToLongBits(this.axisOffset) >>> 32));
715        hash = 53 * hash + (int) (Double.doubleToLongBits(this.stripWidth) ^
716                      (Double.doubleToLongBits(this.stripWidth) >>> 32));
717        hash = 53 * hash + (this.stripOutlineVisible ? 1 : 0);
718        hash = 53 * hash + HashUtils.hashCodeForPaint(this.stripOutlinePaint);
719        hash = 53 * hash + Objects.hashCode(this.stripOutlineStroke);
720        hash = 53 * hash + HashUtils.hashCodeForPaint(this.backgroundPaint);
721        hash = 53 * hash + this.subdivisions;
722        return hash;
723    }
724
725    /**
726     * Provides serialization support.
727     *
728     * @param stream  the output stream.
729     *
730     * @throws IOException  if there is an I/O error.
731     */
732    private void writeObject(ObjectOutputStream stream) throws IOException {
733        stream.defaultWriteObject();
734        SerialUtils.writePaint(this.backgroundPaint, stream);
735        SerialUtils.writePaint(this.stripOutlinePaint, stream);
736        SerialUtils.writeStroke(this.stripOutlineStroke, stream);
737    }
738
739    /**
740     * Provides serialization support.
741     *
742     * @param stream  the input stream.
743     *
744     * @throws IOException  if there is an I/O error.
745     * @throws ClassNotFoundException  if there is a classpath problem.
746     */
747    private void readObject(ObjectInputStream stream)
748            throws IOException, ClassNotFoundException {
749        stream.defaultReadObject();
750        this.backgroundPaint = SerialUtils.readPaint(stream);
751        this.stripOutlinePaint = SerialUtils.readPaint(stream);
752        this.stripOutlineStroke = SerialUtils.readStroke(stream);
753    }
754
755}