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     * BarRenderer.java
029     * ----------------
030     * (C) Copyright 2002-2009, by Object Refinery Limited.
031     *
032     * Original Author:  David Gilbert (for Object Refinery Limited);
033     * Contributor(s):   Christian W. Zuckschwerdt;
034     *                   Peter Kolb (patch 2497611);
035     *
036     * Changes
037     * -------
038     * 14-Mar-2002 : Version 1 (DG);
039     * 23-May-2002 : Added tooltip generator to renderer (DG);
040     * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
041     * 25-Jun-2002 : Changed constructor to protected and removed redundant
042     *               code (DG);
043     * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
044     *               clip values (DG);
045     * 24-Sep-2002 : Added getLegendItem() method (DG);
046     * 09-Oct-2002 : Modified constructor to include URL generator (DG);
047     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
048     * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
049     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
050     * 25-Mar-2003 : Implemented Serializable (DG);
051     * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
052     * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
053     * 12-Jun-2003 : Updates for item labels (DG);
054     * 30-Jul-2003 : Modified entity constructor (CZ);
055     * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
056     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
057     * 07-Oct-2003 : Added renderer state (DG);
058     * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
059     *               methods (DG);
060     * 28-Oct-2003 : Added support for gradient paint on bars (DG);
061     * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
062     * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
063     *               overriding (DG);
064     * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
065     *               label generators.  Fixed equals() method (DG);
066     * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
067     * 05-Nov-2004 : Modified drawItem() signature (DG);
068     * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
069     * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
070     * 18-May-2005 : Added configurable base value (DG);
071     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
072     * 01-Dec-2005 : Update legend item to use/not use outline (DG);
073     * ------------: JFreeChart 1.0.x ---------------------------------------------
074     * 06-Dec-2005 : Fixed bug 1374222 (JDK 1.4 specific code) (DG);
075     * 11-Jan-2006 : Fixed bug 1401856 (bad rendering for non-zero base) (DG);
076     * 04-Aug-2006 : Fixed bug 1467706 (missing item labels for zero value
077     *               bars) (DG);
078     * 04-Dec-2006 : Fixed bug in rendering to non-primary axis (DG);
079     * 13-Dec-2006 : Add support for GradientPaint display in legend items (DG);
080     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
081     * 11-May-2007 : Check for visibility in getLegendItem() (DG);
082     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
083     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
084     * 07-May-2008 : If minimumBarLength is > 0.0, extend the non-base end of the
085     *               bar (DG);
086     * 17-Jun-2008 : Apply legend shape, font and paint attributes (DG);
087     * 24-Jun-2008 : Added barPainter mechanism (DG);
088     * 26-Jun-2008 : Added crosshair support (DG);
089     * 13-Aug-2008 : Added shadowPaint attribute (DG);
090     * 14-Jan-2009 : Added support for seriesVisible flags (PK);
091     * 03-Feb-2009 : Added defaultShadowsVisible flag - see patch 2511330 (PK);
092     *
093     */
094    
095    package org.jfree.chart.renderer.category;
096    
097    import java.awt.BasicStroke;
098    import java.awt.Color;
099    import java.awt.Font;
100    import java.awt.Graphics2D;
101    import java.awt.Paint;
102    import java.awt.Shape;
103    import java.awt.Stroke;
104    import java.awt.geom.Line2D;
105    import java.awt.geom.Point2D;
106    import java.awt.geom.Rectangle2D;
107    import java.io.IOException;
108    import java.io.ObjectInputStream;
109    import java.io.ObjectOutputStream;
110    import java.io.Serializable;
111    
112    import org.jfree.chart.LegendItem;
113    import org.jfree.chart.axis.CategoryAxis;
114    import org.jfree.chart.axis.ValueAxis;
115    import org.jfree.chart.entity.EntityCollection;
116    import org.jfree.chart.event.RendererChangeEvent;
117    import org.jfree.chart.labels.CategoryItemLabelGenerator;
118    import org.jfree.chart.labels.ItemLabelAnchor;
119    import org.jfree.chart.labels.ItemLabelPosition;
120    import org.jfree.chart.plot.CategoryPlot;
121    import org.jfree.chart.plot.PlotOrientation;
122    import org.jfree.chart.plot.PlotRenderingInfo;
123    import org.jfree.data.Range;
124    import org.jfree.data.category.CategoryDataset;
125    import org.jfree.data.general.DatasetUtilities;
126    import org.jfree.io.SerialUtilities;
127    import org.jfree.text.TextUtilities;
128    import org.jfree.ui.GradientPaintTransformer;
129    import org.jfree.ui.RectangleEdge;
130    import org.jfree.ui.StandardGradientPaintTransformer;
131    import org.jfree.util.ObjectUtilities;
132    import org.jfree.util.PaintUtilities;
133    import org.jfree.util.PublicCloneable;
134    
135    /**
136     * A {@link CategoryItemRenderer} that draws individual data items as bars.
137     * The example shown here is generated by the <code>BarChartDemo1.java</code>
138     * program included in the JFreeChart Demo Collection:
139     * <br><br>
140     * <img src="../../../../../images/BarRendererSample.png"
141     * alt="BarRendererSample.png" />
142     */
143    public class BarRenderer extends AbstractCategoryItemRenderer
144            implements Cloneable, PublicCloneable, Serializable {
145    
146        /** For serialization. */
147        private static final long serialVersionUID = 6000649414965887481L;
148    
149        /** The default item margin percentage. */
150        public static final double DEFAULT_ITEM_MARGIN = 0.20;
151    
152        /**
153         * Constant that controls the minimum width before a bar has an outline
154         * drawn.
155         */
156        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
157    
158        /**
159         * The default bar painter assigned to each new instance of this renderer.
160         *
161         * @since 1.0.11
162         */
163        private static BarPainter defaultBarPainter = new GradientBarPainter();
164    
165        /**
166         * Returns the default bar painter.
167         *
168         * @return The default bar painter.
169         *
170         * @since 1.0.11
171         */
172        public static BarPainter getDefaultBarPainter() {
173            return BarRenderer.defaultBarPainter;
174        }
175    
176        /**
177         * Sets the default bar painter.
178         *
179         * @param painter  the painter (<code>null</code> not permitted).
180         *
181         * @since 1.0.11
182         */
183        public static void setDefaultBarPainter(BarPainter painter) {
184            if (painter == null) {
185                throw new IllegalArgumentException("Null 'painter' argument.");
186            }
187            BarRenderer.defaultBarPainter = painter;
188        }
189    
190        /**
191         * The default value for the initialisation of the shadowsVisible flag.
192         */
193        private static boolean defaultShadowsVisible = true;
194    
195        /**
196         * Returns the default value for the <code>shadowsVisible</code> flag.
197         *
198         * @return A boolean.
199         *
200         * @see #setDefaultShadowsVisible(boolean)
201         *
202         * @since 1.0.13
203         */
204        public static boolean getDefaultShadowsVisible() {
205            return BarRenderer.defaultShadowsVisible;
206        }
207    
208        /**
209         * Sets the default value for the shadows visible flag.
210         *
211         * @param visible  the new value for the default.
212         *
213         * @see #getDefaultShadowsVisible()
214         *
215         * @since 1.0.13
216         */
217        public static void setDefaultShadowsVisible(boolean visible) {
218            BarRenderer.defaultShadowsVisible = visible;
219        }
220    
221        /** The margin between items (bars) within a category. */
222        private double itemMargin;
223    
224        /** A flag that controls whether or not bar outlines are drawn. */
225        private boolean drawBarOutline;
226    
227        /** The maximum bar width as a percentage of the available space. */
228        private double maximumBarWidth;
229    
230        /** The minimum bar length (in Java2D units). */
231        private double minimumBarLength;
232    
233        /**
234         * An optional class used to transform gradient paint objects to fit each
235         * bar.
236         */
237        private GradientPaintTransformer gradientPaintTransformer;
238    
239        /**
240         * The fallback position if a positive item label doesn't fit inside the
241         * bar.
242         */
243        private ItemLabelPosition positiveItemLabelPositionFallback;
244    
245        /**
246         * The fallback position if a negative item label doesn't fit inside the
247         * bar.
248         */
249        private ItemLabelPosition negativeItemLabelPositionFallback;
250    
251        /** The upper clip (axis) value for the axis. */
252        private double upperClip;
253        // TODO:  this needs to move into the renderer state
254    
255        /** The lower clip (axis) value for the axis. */
256        private double lowerClip;
257        // TODO:  this needs to move into the renderer state
258    
259        /** The base value for the bars (defaults to 0.0). */
260        private double base;
261    
262        /**
263         * A flag that controls whether the base value is included in the range
264         * returned by the findRangeBounds() method.
265         */
266        private boolean includeBaseInRange;
267    
268        /**
269         * The bar painter (never <code>null</code>).
270         *
271         * @since 1.0.11
272         */
273        private BarPainter barPainter;
274    
275        /**
276         * The flag that controls whether or not shadows are drawn for the bars.
277         *
278         * @since 1.0.11
279         */
280        private boolean shadowsVisible;
281    
282        /**
283         * The shadow paint.
284         *
285         * @since 1.0.11
286         */
287        private transient Paint shadowPaint;
288    
289        /**
290         * The x-offset for the shadow effect.
291         *
292         * @since 1.0.11
293         */
294        private double shadowXOffset;
295    
296        /**
297         * The y-offset for the shadow effect.
298         *
299         * @since 1.0.11
300         */
301        private double shadowYOffset;
302    
303        /**
304         * Creates a new bar renderer with default settings.
305         */
306        public BarRenderer() {
307            super();
308            this.base = 0.0;
309            this.includeBaseInRange = true;
310            this.itemMargin = DEFAULT_ITEM_MARGIN;
311            this.drawBarOutline = false;
312            this.maximumBarWidth = 1.0;
313                // 100 percent, so it will not apply unless changed
314            this.positiveItemLabelPositionFallback = null;
315            this.negativeItemLabelPositionFallback = null;
316            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
317            this.minimumBarLength = 0.0;
318            setBaseLegendShape(new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0));
319            this.barPainter = getDefaultBarPainter();
320            this.shadowsVisible = getDefaultShadowsVisible();
321            this.shadowPaint = Color.gray;
322            this.shadowXOffset = 4.0;
323            this.shadowYOffset = 4.0;
324        }
325    
326        /**
327         * Returns the base value for the bars.  The default value is
328         * <code>0.0</code>.
329         *
330         * @return The base value for the bars.
331         *
332         * @see #setBase(double)
333         */
334        public double getBase() {
335            return this.base;
336        }
337    
338        /**
339         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
340         * to all registered listeners.
341         *
342         * @param base  the new base value.
343         *
344         * @see #getBase()
345         */
346        public void setBase(double base) {
347            this.base = base;
348            fireChangeEvent();
349        }
350    
351        /**
352         * Returns the item margin as a percentage of the available space for all
353         * bars.
354         *
355         * @return The margin percentage (where 0.10 is ten percent).
356         *
357         * @see #setItemMargin(double)
358         */
359        public double getItemMargin() {
360            return this.itemMargin;
361        }
362    
363        /**
364         * Sets the item margin and sends a {@link RendererChangeEvent} to all
365         * registered listeners.  The value is expressed as a percentage of the
366         * available width for plotting all the bars, with the resulting amount to
367         * be distributed between all the bars evenly.
368         *
369         * @param percent  the margin (where 0.10 is ten percent).
370         *
371         * @see #getItemMargin()
372         */
373        public void setItemMargin(double percent) {
374            this.itemMargin = percent;
375            fireChangeEvent();
376        }
377    
378        /**
379         * Returns a flag that controls whether or not bar outlines are drawn.
380         *
381         * @return A boolean.
382         *
383         * @see #setDrawBarOutline(boolean)
384         */
385        public boolean isDrawBarOutline() {
386            return this.drawBarOutline;
387        }
388    
389        /**
390         * Sets the flag that controls whether or not bar outlines are drawn and
391         * sends a {@link RendererChangeEvent} to all registered listeners.
392         *
393         * @param draw  the flag.
394         *
395         * @see #isDrawBarOutline()
396         */
397        public void setDrawBarOutline(boolean draw) {
398            this.drawBarOutline = draw;
399            fireChangeEvent();
400        }
401    
402        /**
403         * Returns the maximum bar width, as a percentage of the available drawing
404         * space.
405         *
406         * @return The maximum bar width.
407         *
408         * @see #setMaximumBarWidth(double)
409         */
410        public double getMaximumBarWidth() {
411            return this.maximumBarWidth;
412        }
413    
414        /**
415         * Sets the maximum bar width, which is specified as a percentage of the
416         * available space for all bars, and sends a {@link RendererChangeEvent} to
417         * all registered listeners.
418         *
419         * @param percent  the percent (where 0.05 is five percent).
420         *
421         * @see #getMaximumBarWidth()
422         */
423        public void setMaximumBarWidth(double percent) {
424            this.maximumBarWidth = percent;
425            fireChangeEvent();
426        }
427    
428        /**
429         * Returns the minimum bar length (in Java2D units).  The default value is
430         * 0.0.
431         *
432         * @return The minimum bar length.
433         *
434         * @see #setMinimumBarLength(double)
435         */
436        public double getMinimumBarLength() {
437            return this.minimumBarLength;
438        }
439    
440        /**
441         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
442         * all registered listeners.  The minimum bar length is specified in Java2D
443         * units, and can be used to prevent bars that represent very small data
444         * values from disappearing when drawn on the screen.  Typically you would
445         * set this to (say) 0.5 or 1.0 Java 2D units.  Use this attribute with
446         * caution, however, because setting it to a non-zero value will
447         * artificially increase the length of bars representing small values,
448         * which may misrepresent your data.
449         *
450         * @param min  the minimum bar length (in Java2D units, must be >= 0.0).
451         *
452         * @see #getMinimumBarLength()
453         */
454        public void setMinimumBarLength(double min) {
455            if (min < 0.0) {
456                throw new IllegalArgumentException("Requires 'min' >= 0.0");
457            }
458            this.minimumBarLength = min;
459            fireChangeEvent();
460        }
461    
462        /**
463         * Returns the gradient paint transformer (an object used to transform
464         * gradient paint objects to fit each bar).
465         *
466         * @return A transformer (<code>null</code> possible).
467         *
468         * @see #setGradientPaintTransformer(GradientPaintTransformer)
469         */
470        public GradientPaintTransformer getGradientPaintTransformer() {
471            return this.gradientPaintTransformer;
472        }
473    
474        /**
475         * Sets the gradient paint transformer and sends a
476         * {@link RendererChangeEvent} to all registered listeners.
477         *
478         * @param transformer  the transformer (<code>null</code> permitted).
479         *
480         * @see #getGradientPaintTransformer()
481         */
482        public void setGradientPaintTransformer(
483                GradientPaintTransformer transformer) {
484            this.gradientPaintTransformer = transformer;
485            fireChangeEvent();
486        }
487    
488        /**
489         * Returns the fallback position for positive item labels that don't fit
490         * within a bar.
491         *
492         * @return The fallback position (<code>null</code> possible).
493         *
494         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
495         */
496        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
497            return this.positiveItemLabelPositionFallback;
498        }
499    
500        /**
501         * Sets the fallback position for positive item labels that don't fit
502         * within a bar, and sends a {@link RendererChangeEvent} to all registered
503         * listeners.
504         *
505         * @param position  the position (<code>null</code> permitted).
506         *
507         * @see #getPositiveItemLabelPositionFallback()
508         */
509        public void setPositiveItemLabelPositionFallback(
510                ItemLabelPosition position) {
511            this.positiveItemLabelPositionFallback = position;
512            fireChangeEvent();
513        }
514    
515        /**
516         * Returns the fallback position for negative item labels that don't fit
517         * within a bar.
518         *
519         * @return The fallback position (<code>null</code> possible).
520         *
521         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
522         */
523        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
524            return this.negativeItemLabelPositionFallback;
525        }
526    
527        /**
528         * Sets the fallback position for negative item labels that don't fit
529         * within a bar, and sends a {@link RendererChangeEvent} to all registered
530         * listeners.
531         *
532         * @param position  the position (<code>null</code> permitted).
533         *
534         * @see #getNegativeItemLabelPositionFallback()
535         */
536        public void setNegativeItemLabelPositionFallback(
537                ItemLabelPosition position) {
538            this.negativeItemLabelPositionFallback = position;
539            fireChangeEvent();
540        }
541    
542        /**
543         * Returns the flag that controls whether or not the base value for the
544         * bars is included in the range calculated by
545         * {@link #findRangeBounds(CategoryDataset)}.
546         *
547         * @return <code>true</code> if the base is included in the range, and
548         *         <code>false</code> otherwise.
549         *
550         * @since 1.0.1
551         *
552         * @see #setIncludeBaseInRange(boolean)
553         */
554        public boolean getIncludeBaseInRange() {
555            return this.includeBaseInRange;
556        }
557    
558        /**
559         * Sets the flag that controls whether or not the base value for the bars
560         * is included in the range calculated by
561         * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
562         * a {@link RendererChangeEvent} is sent to all registered listeners.
563         *
564         * @param include  the new value for the flag.
565         *
566         * @since 1.0.1
567         *
568         * @see #getIncludeBaseInRange()
569         */
570        public void setIncludeBaseInRange(boolean include) {
571            if (this.includeBaseInRange != include) {
572                this.includeBaseInRange = include;
573                fireChangeEvent();
574            }
575        }
576    
577        /**
578         * Returns the bar painter.
579         *
580         * @return The bar painter (never <code>null</code>).
581         *
582         * @see #setBarPainter(BarPainter)
583         *
584         * @since 1.0.11
585         */
586        public BarPainter getBarPainter() {
587            return this.barPainter;
588        }
589    
590        /**
591         * Sets the bar painter for this renderer and sends a
592         * {@link RendererChangeEvent} to all registered listeners.
593         *
594         * @param painter  the painter (<code>null</code> not permitted).
595         *
596         * @see #getBarPainter()
597         *
598         * @since 1.0.11
599         */
600        public void setBarPainter(BarPainter painter) {
601            if (painter == null) {
602                throw new IllegalArgumentException("Null 'painter' argument.");
603            }
604            this.barPainter = painter;
605            fireChangeEvent();
606        }
607    
608        /**
609         * Returns the flag that controls whether or not shadows are drawn for
610         * the bars.
611         *
612         * @return A boolean.
613         *
614         * @since 1.0.11
615         */
616        public boolean getShadowsVisible() {
617            return this.shadowsVisible;
618        }
619    
620        /**
621         * Sets the flag that controls whether or not shadows are
622         * drawn by the renderer.
623         *
624         * @param visible  the new flag value.
625         *
626         * @since 1.0.11
627         */
628        public void setShadowVisible(boolean visible) {
629            this.shadowsVisible = visible;
630            fireChangeEvent();
631        }
632    
633        /**
634         * Returns the shadow paint.
635         *
636         * @return The shadow paint.
637         *
638         * @see #setShadowPaint(Paint)
639         *
640         * @since 1.0.11
641         */
642        public Paint getShadowPaint() {
643            return this.shadowPaint;
644        }
645    
646        /**
647         * Sets the shadow paint and sends a {@link RendererChangeEvent} to all
648         * registered listeners.
649         *
650         * @param paint  the paint (<code>null</code> not permitted).
651         *
652         * @see #getShadowPaint()
653         *
654         * @since 1.0.11
655         */
656        public void setShadowPaint(Paint paint) {
657            if (paint == null) {
658                throw new IllegalArgumentException("Null 'paint' argument.");
659            }
660            this.shadowPaint = paint;
661            fireChangeEvent();
662        }
663    
664        /**
665         * Returns the shadow x-offset.
666         *
667         * @return The shadow x-offset.
668         *
669         * @since 1.0.11
670         */
671        public double getShadowXOffset() {
672            return this.shadowXOffset;
673        }
674    
675        /**
676         * Sets the x-offset for the bar shadow and sends a
677         * {@link RendererChangeEvent} to all registered listeners.
678         *
679         * @param offset  the offset.
680         *
681         * @since 1.0.11
682         */
683        public void setShadowXOffset(double offset) {
684            this.shadowXOffset = offset;
685            fireChangeEvent();
686        }
687    
688        /**
689         * Returns the shadow y-offset.
690         *
691         * @return The shadow y-offset.
692         *
693         * @since 1.0.11
694         */
695        public double getShadowYOffset() {
696            return this.shadowYOffset;
697        }
698    
699        /**
700         * Sets the y-offset for the bar shadow and sends a
701         * {@link RendererChangeEvent} to all registered listeners.
702         *
703         * @param offset  the offset.
704         *
705         * @since 1.0.11
706         */
707        public void setShadowYOffset(double offset) {
708            this.shadowYOffset = offset;
709            fireChangeEvent();
710        }
711    
712        /**
713         * Returns the lower clip value.  This value is recalculated in the
714         * initialise() method.
715         *
716         * @return The value.
717         */
718        public double getLowerClip() {
719            // TODO:  this attribute should be transferred to the renderer state.
720            return this.lowerClip;
721        }
722    
723        /**
724         * Returns the upper clip value.  This value is recalculated in the
725         * initialise() method.
726         *
727         * @return The value.
728         */
729        public double getUpperClip() {
730            // TODO:  this attribute should be transferred to the renderer state.
731            return this.upperClip;
732        }
733    
734        /**
735         * Initialises the renderer and returns a state object that will be passed
736         * to subsequent calls to the drawItem method.  This method gets called
737         * once at the start of the process of drawing a chart.
738         *
739         * @param g2  the graphics device.
740         * @param dataArea  the area in which the data is to be plotted.
741         * @param plot  the plot.
742         * @param rendererIndex  the renderer index.
743         * @param info  collects chart rendering information for return to caller.
744         *
745         * @return The renderer state.
746         */
747        public CategoryItemRendererState initialise(Graphics2D g2,
748                                                    Rectangle2D dataArea,
749                                                    CategoryPlot plot,
750                                                    int rendererIndex,
751                                                    PlotRenderingInfo info) {
752    
753            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
754                    rendererIndex, info);
755    
756            // get the clipping values...
757            ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
758            this.lowerClip = rangeAxis.getRange().getLowerBound();
759            this.upperClip = rangeAxis.getRange().getUpperBound();
760    
761            // calculate the bar width
762            calculateBarWidth(plot, dataArea, rendererIndex, state);
763    
764            return state;
765    
766        }
767    
768        /**
769         * Calculates the bar width and stores it in the renderer state.
770         *
771         * @param plot  the plot.
772         * @param dataArea  the data area.
773         * @param rendererIndex  the renderer index.
774         * @param state  the renderer state.
775         */
776        protected void calculateBarWidth(CategoryPlot plot,
777                                         Rectangle2D dataArea,
778                                         int rendererIndex,
779                                         CategoryItemRendererState state) {
780    
781            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
782            CategoryDataset dataset = plot.getDataset(rendererIndex);
783            if (dataset != null) {
784                int columns = dataset.getColumnCount();
785                int rows = state.getVisibleSeriesCount() >= 0
786                        ? state.getVisibleSeriesCount() : dataset.getRowCount();
787                double space = 0.0;
788                PlotOrientation orientation = plot.getOrientation();
789                if (orientation == PlotOrientation.HORIZONTAL) {
790                    space = dataArea.getHeight();
791                }
792                else if (orientation == PlotOrientation.VERTICAL) {
793                    space = dataArea.getWidth();
794                }
795                double maxWidth = space * getMaximumBarWidth();
796                double categoryMargin = 0.0;
797                double currentItemMargin = 0.0;
798                if (columns > 1) {
799                    categoryMargin = domainAxis.getCategoryMargin();
800                }
801                if (rows > 1) {
802                    currentItemMargin = getItemMargin();
803                }
804                double used = space * (1 - domainAxis.getLowerMargin()
805                                         - domainAxis.getUpperMargin()
806                                         - categoryMargin - currentItemMargin);
807                if ((rows * columns) > 0) {
808                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
809                }
810                else {
811                    state.setBarWidth(Math.min(used, maxWidth));
812                }
813            }
814        }
815    
816        /**
817         * Calculates the coordinate of the first "side" of a bar.  This will be
818         * the minimum x-coordinate for a vertical bar, and the minimum
819         * y-coordinate for a horizontal bar.
820         *
821         * @param plot  the plot.
822         * @param orientation  the plot orientation.
823         * @param dataArea  the data area.
824         * @param domainAxis  the domain axis.
825         * @param state  the renderer state (has the bar width precalculated).
826         * @param row  the row index.
827         * @param column  the column index.
828         *
829         * @return The coordinate.
830         */
831        protected double calculateBarW0(CategoryPlot plot,
832                                        PlotOrientation orientation,
833                                        Rectangle2D dataArea,
834                                        CategoryAxis domainAxis,
835                                        CategoryItemRendererState state,
836                                        int row,
837                                        int column) {
838            // calculate bar width...
839            double space = 0.0;
840            if (orientation == PlotOrientation.HORIZONTAL) {
841                space = dataArea.getHeight();
842            }
843            else {
844                space = dataArea.getWidth();
845            }
846            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
847                    dataArea, plot.getDomainAxisEdge());
848            int seriesCount = state.getVisibleSeriesCount() >= 0
849                    ? state.getVisibleSeriesCount() : getRowCount();
850            int categoryCount = getColumnCount();
851            if (seriesCount > 1) {
852                double seriesGap = space * getItemMargin()
853                                   / (categoryCount * (seriesCount - 1));
854                double seriesW = calculateSeriesWidth(space, domainAxis,
855                        categoryCount, seriesCount);
856                barW0 = barW0 + row * (seriesW + seriesGap)
857                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
858            }
859            else {
860                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
861                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
862                        / 2.0;
863            }
864            return barW0;
865        }
866    
867        /**
868         * Calculates the coordinates for the length of a single bar.
869         *
870         * @param value  the value represented by the bar.
871         *
872         * @return The coordinates for each end of the bar (or <code>null</code> if
873         *         the bar is not visible for the current axis range).
874         */
875        protected double[] calculateBarL0L1(double value) {
876            double lclip = getLowerClip();
877            double uclip = getUpperClip();
878            double barLow = Math.min(this.base, value);
879            double barHigh = Math.max(this.base, value);
880            if (barHigh < lclip) {  // bar is not visible
881                return null;
882            }
883            if (barLow > uclip) {   // bar is not visible
884                return null;
885            }
886            barLow = Math.max(barLow, lclip);
887            barHigh = Math.min(barHigh, uclip);
888            return new double[] {barLow, barHigh};
889        }
890    
891        /**
892         * Returns the range of values the renderer requires to display all the
893         * items from the specified dataset.  This takes into account the range
894         * of values in the dataset, plus the flag that determines whether or not
895         * the base value for the bars should be included in the range.
896         *
897         * @param dataset  the dataset (<code>null</code> permitted).
898         *
899         * @return The range (or <code>null</code> if the dataset is
900         *         <code>null</code> or empty).
901         */
902        public Range findRangeBounds(CategoryDataset dataset) {
903            if (dataset == null) {
904                return null;
905            }
906            Range result = DatasetUtilities.findRangeBounds(dataset);
907            if (result != null) {
908                if (this.includeBaseInRange) {
909                    result = Range.expandToInclude(result, this.base);
910                }
911            }
912            return result;
913        }
914    
915        /**
916         * Returns a legend item for a series.
917         *
918         * @param datasetIndex  the dataset index (zero-based).
919         * @param series  the series index (zero-based).
920         *
921         * @return The legend item (possibly <code>null</code>).
922         */
923        public LegendItem getLegendItem(int datasetIndex, int series) {
924    
925            CategoryPlot cp = getPlot();
926            if (cp == null) {
927                return null;
928            }
929    
930            // check that a legend item needs to be displayed...
931            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
932                return null;
933            }
934    
935            CategoryDataset dataset = cp.getDataset(datasetIndex);
936            String label = getLegendItemLabelGenerator().generateLabel(dataset,
937                    series);
938            String description = label;
939            String toolTipText = null;
940            if (getLegendItemToolTipGenerator() != null) {
941                toolTipText = getLegendItemToolTipGenerator().generateLabel(
942                        dataset, series);
943            }
944            String urlText = null;
945            if (getLegendItemURLGenerator() != null) {
946                urlText = getLegendItemURLGenerator().generateLabel(dataset,
947                        series);
948            }
949            Shape shape = lookupLegendShape(series);
950            Paint paint = lookupSeriesPaint(series);
951            Paint outlinePaint = lookupSeriesOutlinePaint(series);
952            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
953    
954            LegendItem result = new LegendItem(label, description, toolTipText,
955                    urlText, true, shape, true, paint, isDrawBarOutline(),
956                    outlinePaint, outlineStroke, false, new Line2D.Float(),
957                    new BasicStroke(1.0f), Color.black);
958            result.setLabelFont(lookupLegendTextFont(series));
959            Paint labelPaint = lookupLegendTextPaint(series);
960            if (labelPaint != null) {
961                result.setLabelPaint(labelPaint);
962            }
963            result.setDataset(dataset);
964            result.setDatasetIndex(datasetIndex);
965            result.setSeriesKey(dataset.getRowKey(series));
966            result.setSeriesIndex(series);
967            if (this.gradientPaintTransformer != null) {
968                result.setFillPaintTransformer(this.gradientPaintTransformer);
969            }
970            return result;
971        }
972    
973        /**
974         * Draws the bar for a single (series, category) data item.
975         *
976         * @param g2  the graphics device.
977         * @param state  the renderer state.
978         * @param dataArea  the data area.
979         * @param plot  the plot.
980         * @param domainAxis  the domain axis.
981         * @param rangeAxis  the range axis.
982         * @param dataset  the dataset.
983         * @param row  the row index (zero-based).
984         * @param column  the column index (zero-based).
985         * @param pass  the pass index.
986         */
987        public void drawItem(Graphics2D g2,
988                             CategoryItemRendererState state,
989                             Rectangle2D dataArea,
990                             CategoryPlot plot,
991                             CategoryAxis domainAxis,
992                             ValueAxis rangeAxis,
993                             CategoryDataset dataset,
994                             int row,
995                             int column,
996                             int pass) {
997    
998            // nothing is drawn if the row index is not included in the list with
999            // the indices of the visible rows...
1000            int visibleRow = state.getVisibleSeriesIndex(row);
1001            if (visibleRow < 0) {
1002                return;
1003            }
1004            // nothing is drawn for null values...
1005            Number dataValue = dataset.getValue(row, column);
1006            if (dataValue == null) {
1007                return;
1008            }
1009    
1010            final double value = dataValue.doubleValue();
1011            PlotOrientation orientation = plot.getOrientation();
1012            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
1013                    state, visibleRow, column);
1014            double[] barL0L1 = calculateBarL0L1(value);
1015            if (barL0L1 == null) {
1016                return;  // the bar is not visible
1017            }
1018    
1019            RectangleEdge edge = plot.getRangeAxisEdge();
1020            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
1021            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
1022    
1023            // in the following code, barL0 is (in Java2D coordinates) the LEFT
1024            // end of the bar for a horizontal bar chart, and the TOP end of the
1025            // bar for a vertical bar chart.  Whether this is the BASE of the bar
1026            // or not depends also on (a) whether the data value is 'negative'
1027            // relative to the base value and (b) whether or not the range axis is
1028            // inverted.  This only matters if/when we apply the minimumBarLength
1029            // attribute, because we should extend the non-base end of the bar
1030            boolean positive = (value >= this.base);
1031            boolean inverted = rangeAxis.isInverted();
1032            double barL0 = Math.min(transL0, transL1);
1033            double barLength = Math.abs(transL1 - transL0);
1034            double barLengthAdj = 0.0;
1035            if (barLength > 0.0 && barLength < getMinimumBarLength()) {
1036                barLengthAdj = getMinimumBarLength() - barLength;
1037            }
1038            double barL0Adj = 0.0;
1039            RectangleEdge barBase;
1040            if (orientation == PlotOrientation.HORIZONTAL) {
1041                if (positive && inverted || !positive && !inverted) {
1042                    barL0Adj = barLengthAdj;
1043                    barBase = RectangleEdge.RIGHT;
1044                }
1045                else {
1046                    barBase = RectangleEdge.LEFT;
1047                }
1048            }
1049            else {
1050                if (positive && !inverted || !positive && inverted) {
1051                    barL0Adj = barLengthAdj;
1052                    barBase = RectangleEdge.BOTTOM;
1053                }
1054                else {
1055                    barBase = RectangleEdge.TOP;
1056                }
1057            }
1058    
1059            // draw the bar...
1060            Rectangle2D bar = null;
1061            if (orientation == PlotOrientation.HORIZONTAL) {
1062                bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
1063                        barLength + barLengthAdj, state.getBarWidth());
1064            }
1065            else {
1066                bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
1067                        state.getBarWidth(), barLength + barLengthAdj);
1068            }
1069            if (getShadowsVisible()) {
1070                this.barPainter.paintBarShadow(g2, this, row, column, bar, barBase,
1071                    true);
1072            }
1073            this.barPainter.paintBar(g2, this, row, column, bar, barBase);
1074    
1075            CategoryItemLabelGenerator generator = getItemLabelGenerator(row,
1076                    column);
1077            if (generator != null && isItemLabelVisible(row, column)) {
1078                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
1079                        (value < 0.0));
1080            }
1081    
1082            // submit the current data point as a crosshair candidate
1083            int datasetIndex = plot.indexOf(dataset);
1084            updateCrosshairValues(state.getCrosshairState(),
1085                    dataset.getRowKey(row), dataset.getColumnKey(column), value,
1086                    datasetIndex, barW0, barL0, orientation);
1087    
1088            // add an item entity, if this information is being collected
1089            EntityCollection entities = state.getEntityCollection();
1090            if (entities != null) {
1091                addItemEntity(entities, dataset, row, column, bar);
1092            }
1093    
1094        }
1095    
1096        /**
1097         * Calculates the available space for each series.
1098         *
1099         * @param space  the space along the entire axis (in Java2D units).
1100         * @param axis  the category axis.
1101         * @param categories  the number of categories.
1102         * @param series  the number of series.
1103         *
1104         * @return The width of one series.
1105         */
1106        protected double calculateSeriesWidth(double space, CategoryAxis axis,
1107                                              int categories, int series) {
1108            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
1109                                - axis.getUpperMargin();
1110            if (categories > 1) {
1111                factor = factor - axis.getCategoryMargin();
1112            }
1113            return (space * factor) / (categories * series);
1114        }
1115    
1116        /**
1117         * Draws an item label.  This method is overridden so that the bar can be
1118         * used to calculate the label anchor point.
1119         *
1120         * @param g2  the graphics device.
1121         * @param data  the dataset.
1122         * @param row  the row.
1123         * @param column  the column.
1124         * @param plot  the plot.
1125         * @param generator  the label generator.
1126         * @param bar  the bar.
1127         * @param negative  a flag indicating a negative value.
1128         */
1129        protected void drawItemLabel(Graphics2D g2,
1130                                     CategoryDataset data,
1131                                     int row,
1132                                     int column,
1133                                     CategoryPlot plot,
1134                                     CategoryItemLabelGenerator generator,
1135                                     Rectangle2D bar,
1136                                     boolean negative) {
1137    
1138            String label = generator.generateLabel(data, row, column);
1139            if (label == null) {
1140                return;  // nothing to do
1141            }
1142    
1143            Font labelFont = getItemLabelFont(row, column);
1144            g2.setFont(labelFont);
1145            Paint paint = getItemLabelPaint(row, column);
1146            g2.setPaint(paint);
1147    
1148            // find out where to place the label...
1149            ItemLabelPosition position = null;
1150            if (!negative) {
1151                position = getPositiveItemLabelPosition(row, column);
1152            }
1153            else {
1154                position = getNegativeItemLabelPosition(row, column);
1155            }
1156    
1157            // work out the label anchor point...
1158            Point2D anchorPoint = calculateLabelAnchorPoint(
1159                    position.getItemLabelAnchor(), bar, plot.getOrientation());
1160    
1161            if (isInternalAnchor(position.getItemLabelAnchor())) {
1162                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
1163                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1164                        position.getTextAnchor(), position.getAngle(),
1165                        position.getRotationAnchor());
1166    
1167                if (bounds != null) {
1168                    if (!bar.contains(bounds.getBounds2D())) {
1169                        if (!negative) {
1170                            position = getPositiveItemLabelPositionFallback();
1171                        }
1172                        else {
1173                            position = getNegativeItemLabelPositionFallback();
1174                        }
1175                        if (position != null) {
1176                            anchorPoint = calculateLabelAnchorPoint(
1177                                    position.getItemLabelAnchor(), bar,
1178                                    plot.getOrientation());
1179                        }
1180                    }
1181                }
1182    
1183            }
1184    
1185            if (position != null) {
1186                TextUtilities.drawRotatedString(label, g2,
1187                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
1188                        position.getTextAnchor(), position.getAngle(),
1189                        position.getRotationAnchor());
1190            }
1191        }
1192    
1193        /**
1194         * Calculates the item label anchor point.
1195         *
1196         * @param anchor  the anchor.
1197         * @param bar  the bar.
1198         * @param orientation  the plot orientation.
1199         *
1200         * @return The anchor point.
1201         */
1202        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
1203                                                  Rectangle2D bar,
1204                                                  PlotOrientation orientation) {
1205    
1206            Point2D result = null;
1207            double offset = getItemLabelAnchorOffset();
1208            double x0 = bar.getX() - offset;
1209            double x1 = bar.getX();
1210            double x2 = bar.getX() + offset;
1211            double x3 = bar.getCenterX();
1212            double x4 = bar.getMaxX() - offset;
1213            double x5 = bar.getMaxX();
1214            double x6 = bar.getMaxX() + offset;
1215    
1216            double y0 = bar.getMaxY() + offset;
1217            double y1 = bar.getMaxY();
1218            double y2 = bar.getMaxY() - offset;
1219            double y3 = bar.getCenterY();
1220            double y4 = bar.getMinY() + offset;
1221            double y5 = bar.getMinY();
1222            double y6 = bar.getMinY() - offset;
1223    
1224            if (anchor == ItemLabelAnchor.CENTER) {
1225                result = new Point2D.Double(x3, y3);
1226            }
1227            else if (anchor == ItemLabelAnchor.INSIDE1) {
1228                result = new Point2D.Double(x4, y4);
1229            }
1230            else if (anchor == ItemLabelAnchor.INSIDE2) {
1231                result = new Point2D.Double(x4, y4);
1232            }
1233            else if (anchor == ItemLabelAnchor.INSIDE3) {
1234                result = new Point2D.Double(x4, y3);
1235            }
1236            else if (anchor == ItemLabelAnchor.INSIDE4) {
1237                result = new Point2D.Double(x4, y2);
1238            }
1239            else if (anchor == ItemLabelAnchor.INSIDE5) {
1240                result = new Point2D.Double(x4, y2);
1241            }
1242            else if (anchor == ItemLabelAnchor.INSIDE6) {
1243                result = new Point2D.Double(x3, y2);
1244            }
1245            else if (anchor == ItemLabelAnchor.INSIDE7) {
1246                result = new Point2D.Double(x2, y2);
1247            }
1248            else if (anchor == ItemLabelAnchor.INSIDE8) {
1249                result = new Point2D.Double(x2, y2);
1250            }
1251            else if (anchor == ItemLabelAnchor.INSIDE9) {
1252                result = new Point2D.Double(x2, y3);
1253            }
1254            else if (anchor == ItemLabelAnchor.INSIDE10) {
1255                result = new Point2D.Double(x2, y4);
1256            }
1257            else if (anchor == ItemLabelAnchor.INSIDE11) {
1258                result = new Point2D.Double(x2, y4);
1259            }
1260            else if (anchor == ItemLabelAnchor.INSIDE12) {
1261                result = new Point2D.Double(x3, y4);
1262            }
1263            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
1264                result = new Point2D.Double(x5, y6);
1265            }
1266            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
1267                result = new Point2D.Double(x6, y5);
1268            }
1269            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
1270                result = new Point2D.Double(x6, y3);
1271            }
1272            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
1273                result = new Point2D.Double(x6, y1);
1274            }
1275            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
1276                result = new Point2D.Double(x5, y0);
1277            }
1278            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
1279                result = new Point2D.Double(x3, y0);
1280            }
1281            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
1282                result = new Point2D.Double(x1, y0);
1283            }
1284            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
1285                result = new Point2D.Double(x0, y1);
1286            }
1287            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
1288                result = new Point2D.Double(x0, y3);
1289            }
1290            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
1291                result = new Point2D.Double(x0, y5);
1292            }
1293            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
1294                result = new Point2D.Double(x1, y6);
1295            }
1296            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
1297                result = new Point2D.Double(x3, y6);
1298            }
1299    
1300            return result;
1301    
1302        }
1303    
1304        /**
1305         * Returns <code>true</code> if the specified anchor point is inside a bar.
1306         *
1307         * @param anchor  the anchor point.
1308         *
1309         * @return A boolean.
1310         */
1311        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
1312            return anchor == ItemLabelAnchor.CENTER
1313                   || anchor == ItemLabelAnchor.INSIDE1
1314                   || anchor == ItemLabelAnchor.INSIDE2
1315                   || anchor == ItemLabelAnchor.INSIDE3
1316                   || anchor == ItemLabelAnchor.INSIDE4
1317                   || anchor == ItemLabelAnchor.INSIDE5
1318                   || anchor == ItemLabelAnchor.INSIDE6
1319                   || anchor == ItemLabelAnchor.INSIDE7
1320                   || anchor == ItemLabelAnchor.INSIDE8
1321                   || anchor == ItemLabelAnchor.INSIDE9
1322                   || anchor == ItemLabelAnchor.INSIDE10
1323                   || anchor == ItemLabelAnchor.INSIDE11
1324                   || anchor == ItemLabelAnchor.INSIDE12;
1325        }
1326    
1327        /**
1328         * Tests this instance for equality with an arbitrary object.
1329         *
1330         * @param obj  the object (<code>null</code> permitted).
1331         *
1332         * @return A boolean.
1333         */
1334        public boolean equals(Object obj) {
1335            if (obj == this) {
1336                return true;
1337            }
1338            if (!(obj instanceof BarRenderer)) {
1339                return false;
1340            }
1341            BarRenderer that = (BarRenderer) obj;
1342            if (this.base != that.base) {
1343                return false;
1344            }
1345            if (this.itemMargin != that.itemMargin) {
1346                return false;
1347            }
1348            if (this.drawBarOutline != that.drawBarOutline) {
1349                return false;
1350            }
1351            if (this.maximumBarWidth != that.maximumBarWidth) {
1352                return false;
1353            }
1354            if (this.minimumBarLength != that.minimumBarLength) {
1355                return false;
1356            }
1357            if (!ObjectUtilities.equal(this.gradientPaintTransformer,
1358                    that.gradientPaintTransformer)) {
1359                return false;
1360            }
1361            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
1362                that.positiveItemLabelPositionFallback)) {
1363                return false;
1364            }
1365            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
1366                that.negativeItemLabelPositionFallback)) {
1367                return false;
1368            }
1369            if (!this.barPainter.equals(that.barPainter)) {
1370                return false;
1371            }
1372            if (this.shadowsVisible != that.shadowsVisible) {
1373                return false;
1374            }
1375            if (!PaintUtilities.equal(this.shadowPaint, that.shadowPaint)) {
1376                return false;
1377            }
1378            if (this.shadowXOffset != that.shadowXOffset) {
1379                return false;
1380            }
1381            if (this.shadowYOffset != that.shadowYOffset) {
1382                return false;
1383            }
1384            return super.equals(obj);
1385        }
1386    
1387        /**
1388         * Provides serialization support.
1389         *
1390         * @param stream  the output stream.
1391         *
1392         * @throws IOException  if there is an I/O error.
1393         */
1394        private void writeObject(ObjectOutputStream stream) throws IOException {
1395            stream.defaultWriteObject();
1396            SerialUtilities.writePaint(this.shadowPaint, stream);
1397        }
1398    
1399        /**
1400         * Provides serialization support.
1401         *
1402         * @param stream  the input stream.
1403         *
1404         * @throws IOException  if there is an I/O error.
1405         * @throws ClassNotFoundException  if there is a classpath problem.
1406         */
1407        private void readObject(ObjectInputStream stream)
1408                throws IOException, ClassNotFoundException {
1409            stream.defaultReadObject();
1410            this.shadowPaint = SerialUtilities.readPaint(stream);
1411        }
1412    
1413    }