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