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