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