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