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