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
095package org.jfree.chart.renderer.category;
096
097import java.awt.BasicStroke;
098import java.awt.Color;
099import java.awt.Font;
100import java.awt.Graphics2D;
101import java.awt.Paint;
102import java.awt.Shape;
103import java.awt.Stroke;
104import java.awt.geom.Line2D;
105import java.awt.geom.Point2D;
106import java.awt.geom.Rectangle2D;
107import java.io.IOException;
108import java.io.ObjectInputStream;
109import java.io.ObjectOutputStream;
110import java.io.Serializable;
111
112import org.jfree.chart.LegendItem;
113import org.jfree.chart.axis.CategoryAxis;
114import org.jfree.chart.axis.ValueAxis;
115import org.jfree.chart.entity.EntityCollection;
116import org.jfree.chart.event.RendererChangeEvent;
117import org.jfree.chart.labels.CategoryItemLabelGenerator;
118import org.jfree.chart.labels.ItemLabelAnchor;
119import org.jfree.chart.labels.ItemLabelPosition;
120import org.jfree.chart.plot.CategoryPlot;
121import org.jfree.chart.plot.PlotOrientation;
122import org.jfree.chart.plot.PlotRenderingInfo;
123import org.jfree.data.Range;
124import org.jfree.data.category.CategoryDataset;
125import org.jfree.io.SerialUtilities;
126import org.jfree.text.TextUtilities;
127import org.jfree.ui.GradientPaintTransformer;
128import org.jfree.ui.RectangleEdge;
129import org.jfree.ui.StandardGradientPaintTransformer;
130import org.jfree.util.ObjectUtilities;
131import org.jfree.util.PaintUtilities;
132import 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 */
142public 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}