    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     *
    086     */
    087
    088    package org.jfree.chart.renderer.category;
    089
    090    import java.awt.BasicStroke;
    091    import java.awt.Color;
    092    import java.awt.Font;
    093    import java.awt.GradientPaint;
    094    import java.awt.Graphics2D;
    095    import java.awt.Paint;
    096    import java.awt.Shape;
    097    import java.awt.Stroke;
    098    import java.awt.geom.Line2D;
    099    import java.awt.geom.Point2D;
    100    import java.awt.geom.Rectangle2D;
    101    import java.io.Serializable;
    102
    103    import org.jfree.chart.LegendItem;
    104    import org.jfree.chart.axis.CategoryAxis;
    105    import org.jfree.chart.axis.ValueAxis;
    106    import org.jfree.chart.entity.EntityCollection;
    107    import org.jfree.chart.event.RendererChangeEvent;
    108    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    109    import org.jfree.chart.labels.ItemLabelAnchor;
    110    import org.jfree.chart.labels.ItemLabelPosition;
    111    import org.jfree.chart.plot.CategoryPlot;
    112    import org.jfree.chart.plot.PlotOrientation;
    113    import org.jfree.chart.plot.PlotRenderingInfo;
    114    import org.jfree.data.Range;
    115    import org.jfree.data.category.CategoryDataset;
    116    import org.jfree.data.general.DatasetUtilities;
    117    import org.jfree.text.TextUtilities;
    118    import org.jfree.ui.GradientPaintTransformer;
    119    import org.jfree.ui.RectangleEdge;
    120    import org.jfree.ui.StandardGradientPaintTransformer;
    121    import org.jfree.util.ObjectUtilities;
    122    import org.jfree.util.PublicCloneable;
    123
    124    /**
    125     * A {@link CategoryItemRenderer} that draws individual data items as bars.
    126     */
    127    public class BarRenderer extends AbstractCategoryItemRenderer
    128                             implements Cloneable, PublicCloneable, Serializable {
    129
    130        /** For serialization. */
    131        private static final long serialVersionUID = 6000649414965887481L;
    132
    133        /** The default item margin percentage. */
    134        public static final double DEFAULT_ITEM_MARGIN = 0.20;
    135
    136        /**
    137         * Constant that controls the minimum width before a bar has an outline
    138         * drawn.
    139         */
    140        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
    141
    142        /** The margin between items (bars) within a category. */
    143        private double itemMargin;
    144
    145        /** A flag that controls whether or not bar outlines are drawn. */
    146        private boolean drawBarOutline;
    147
    148        /** The maximum bar width as a percentage of the available space. */
    149        private double maximumBarWidth;
    150
    151        /** The minimum bar length (in Java2D units). */
    152        private double minimumBarLength;
    153
    154        /**
    155         * An optional class used to transform gradient paint objects to fit each
    156         * bar.
    157         */
    158        private GradientPaintTransformer gradientPaintTransformer;
    159
    160        /**
    161         * The fallback position if a positive item label doesn't fit inside the
    162         * bar.
    163         */
    164        private ItemLabelPosition positiveItemLabelPositionFallback;
    165
    166        /**
    167         * The fallback position if a negative item label doesn't fit inside the
    168         * bar.
    169         */
    170        private ItemLabelPosition negativeItemLabelPositionFallback;
    171
    172        /** The upper clip (axis) value for the axis. */
    173        private double upperClip;
    174        // TODO:  this needs to move into the renderer state
    175
    176        /** The lower clip (axis) value for the axis. */
    177        private double lowerClip;
    178        // TODO:  this needs to move into the renderer state
    179
    180        /** The base value for the bars (defaults to 0.0). */
    181        private double base;
    182
    183        /**
    184         * A flag that controls whether the base value is included in the range
    185         * returned by the findRangeBounds() method.
    186         */
    187        private boolean includeBaseInRange;
    188
    189        /**
    190         * Creates a new bar renderer with default settings.
    191         */
    192        public BarRenderer() {
    193            super();
    194            this.base = 0.0;
    195            this.includeBaseInRange = true;
    196            this.itemMargin = DEFAULT_ITEM_MARGIN;
    197            this.drawBarOutline = false;
    198            this.maximumBarWidth = 1.0;
    199                // 100 percent, so it will not apply unless changed
    200            this.positiveItemLabelPositionFallback = null;
    201            this.negativeItemLabelPositionFallback = null;
    202            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    203            this.minimumBarLength = 0.0;
    204        }
    205
    206        /**
    207         * Returns the base value for the bars.  The default value is
    208         * <code>0.0</code>.
    209         *
    210         * @return The base value for the bars.
    211         *
    212         * @see #setBase(double)
    213         */
    214        public double getBase() {
    215            return this.base;
    216        }
    217
    218        /**
    219         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    220         * to all registered listeners.
    221         *
    222         * @param base  the new base value.
    223         *
    224         * @see #getBase()
    225         */
    226        public void setBase(double base) {
    227            this.base = base;
    228            fireChangeEvent();
    229        }
    230
    231        /**
    232         * Returns the item margin as a percentage of the available space for all
    233         * bars.
    234         *
    235         * @return The margin percentage (where 0.10 is ten percent).
    236         *
    237         * @see #setItemMargin(double)
    238         */
    239        public double getItemMargin() {
    240            return this.itemMargin;
    241        }
    242
    243        /**
    244         * Sets the item margin and sends a {@link RendererChangeEvent} to all
    245         * registered listeners.  The value is expressed as a percentage of the
    246         * available width for plotting all the bars, with the resulting amount to
    247         * be distributed between all the bars evenly.
    248         *
    249         * @param percent  the margin (where 0.10 is ten percent).
    250         *
    251         * @see #getItemMargin()
    252         */
    253        public void setItemMargin(double percent) {
    254            this.itemMargin = percent;
    255            fireChangeEvent();
    256        }
    257
    258        /**
    259         * Returns a flag that controls whether or not bar outlines are drawn.
    260         *
    261         * @return A boolean.
    262         *
    263         * @see #setDrawBarOutline(boolean)
    264         */
    265        public boolean isDrawBarOutline() {
    266            return this.drawBarOutline;
    267        }
    268
    269        /**
    270         * Sets the flag that controls whether or not bar outlines are drawn and
    271         * sends a {@link RendererChangeEvent} to all registered listeners.
    272         *
    273         * @param draw  the flag.
    274         *
    275         * @see #isDrawBarOutline()
    276         */
    277        public void setDrawBarOutline(boolean draw) {
    278            this.drawBarOutline = draw;
    279            fireChangeEvent();
    280        }
    281
    282        /**
    283         * Returns the maximum bar width, as a percentage of the available drawing
    284         * space.
    285         *
    286         * @return The maximum bar width.
    287         *
    288         * @see #setMaximumBarWidth(double)
    289         */
    290        public double getMaximumBarWidth() {
    291            return this.maximumBarWidth;
    292        }
    293
    294        /**
    295         * Sets the maximum bar width, which is specified as a percentage of the
    296         * available space for all bars, and sends a {@link RendererChangeEvent} to
    297         * all registered listeners.
    298         *
    299         * @param percent  the percent (where 0.05 is five percent).
    300         *
    301         * @see #getMaximumBarWidth()
    302         */
    303        public void setMaximumBarWidth(double percent) {
    304            this.maximumBarWidth = percent;
    305            fireChangeEvent();
    306        }
    307
    308        /**
    309         * Returns the minimum bar length (in Java2D units).  The default value is
    310         * 0.0.
    311         *
    312         * @return The minimum bar length.
    313         *
    314         * @see #setMinimumBarLength(double)
    315         */
    316        public double getMinimumBarLength() {
    317            return this.minimumBarLength;
    318        }
    319
    320        /**
    321         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
    322         * all registered listeners.  The minimum bar length is specified in Java2D
    323         * units, and can be used to prevent bars that represent very small data
    324         * values from disappearing when drawn on the screen.  Typically you would
    325         * set this to (say) 0.5 or 1.0 Java 2D units.  Use this attribute with
    326         * caution, however, because setting it to a non-zero value will
    327         * artificially increase the length of bars representing small values,
    328         * which may misrepresent your data.
    329         *
    330         * @param min  the minimum bar length (in Java2D units, must be >= 0.0).
    331         *
    332         * @see #getMinimumBarLength()
    333         */
    334        public void setMinimumBarLength(double min) {
    335            if (min < 0.0) {
    336                throw new IllegalArgumentException("Requires 'min' >= 0.0");
    337            }
    338            this.minimumBarLength = min;
    339            fireChangeEvent();
    340        }
    341
    342        /**
    343         * Returns the gradient paint transformer (an object used to transform
    344         * gradient paint objects to fit each bar).
    345         *
    346         * @return A transformer (<code>null</code> possible).
    347         *
    348         * @see #setGradientPaintTransformer(GradientPaintTransformer)
    349         */
    350        public GradientPaintTransformer getGradientPaintTransformer() {
    351            return this.gradientPaintTransformer;
    352        }
    353
    354        /**
    355         * Sets the gradient paint transformer and sends a
    356         * {@link RendererChangeEvent} to all registered listeners.
    357         *
    358         * @param transformer  the transformer (<code>null</code> permitted).
    359         *
    360         * @see #getGradientPaintTransformer()
    361         */
    362        public void setGradientPaintTransformer(
    363                GradientPaintTransformer transformer) {
    364            this.gradientPaintTransformer = transformer;
    365            fireChangeEvent();
    366        }
    367
    368        /**
    369         * Returns the fallback position for positive item labels that don't fit
    370         * within a bar.
    371         *
    372         * @return The fallback position (<code>null</code> possible).
    373         *
    374         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    375         */
    376        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    377            return this.positiveItemLabelPositionFallback;
    378        }
    379
    380        /**
    381         * Sets the fallback position for positive item labels that don't fit
    382         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    383         * listeners.
    384         *
    385         * @param position  the position (<code>null</code> permitted).
    386         *
    387         * @see #getPositiveItemLabelPositionFallback()
    388         */
    389        public void setPositiveItemLabelPositionFallback(
    390                ItemLabelPosition position) {
    391            this.positiveItemLabelPositionFallback = position;
    392            fireChangeEvent();
    393        }
    394
    395        /**
    396         * Returns the fallback position for negative item labels that don't fit
    397         * within a bar.
    398         *
    399         * @return The fallback position (<code>null</code> possible).
    400         *
    401         * @see #setPositiveItemLabelPositionFallback(ItemLabelPosition)
    402         */
    403        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    404            return this.negativeItemLabelPositionFallback;
    405        }
    406
    407        /**
    408         * Sets the fallback position for negative item labels that don't fit
    409         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    410         * listeners.
    411         *
    412         * @param position  the position (<code>null</code> permitted).
    413         *
    414         * @see #getNegativeItemLabelPositionFallback()
    415         */
    416        public void setNegativeItemLabelPositionFallback(
    417                ItemLabelPosition position) {
    418            this.negativeItemLabelPositionFallback = position;
    419            fireChangeEvent();
    420        }
    421
    422        /**
    423         * Returns the flag that controls whether or not the base value for the
    424         * bars is included in the range calculated by
    425         * {@link #findRangeBounds(CategoryDataset)}.
    426         *
    427         * @return <code>true</code> if the base is included in the range, and
    428         *         <code>false</code> otherwise.
    429         *
    430         * @since 1.0.1
    431         *
    432         * @see #setIncludeBaseInRange(boolean)
    433         */
    434        public boolean getIncludeBaseInRange() {
    435            return this.includeBaseInRange;
    436        }
    437
    438        /**
    439         * Sets the flag that controls whether or not the base value for the bars
    440         * is included in the range calculated by
    441         * {@link #findRangeBounds(CategoryDataset)}.  If the flag is changed,
    442         * a {@link RendererChangeEvent} is sent to all registered listeners.
    443         *
    444         * @param include  the new value for the flag.
    445         *
    446         * @since 1.0.1
    447         *
    448         * @see #getIncludeBaseInRange()
    449         */
    450        public void setIncludeBaseInRange(boolean include) {
    451            if (this.includeBaseInRange != include) {
    452                this.includeBaseInRange = include;
    453                fireChangeEvent();
    454            }
    455        }
    456
    457        /**
    458         * Returns the lower clip value.  This value is recalculated in the
    459         * initialise() method.
    460         *
    461         * @return The value.
    462         */
    463        public double getLowerClip() {
    464            // TODO:  this attribute should be transferred to the renderer state.
    465            return this.lowerClip;
    466        }
    467
    468        /**
    469         * Returns the upper clip value.  This value is recalculated in the
    470         * initialise() method.
    471         *
    472         * @return The value.
    473         */
    474        public double getUpperClip() {
    475            // TODO:  this attribute should be transferred to the renderer state.
    476            return this.upperClip;
    477        }
    478
    479        /**
    480         * Initialises the renderer and returns a state object that will be passed
    481         * to subsequent calls to the drawItem method.  This method gets called
    482         * once at the start of the process of drawing a chart.
    483         *
    484         * @param g2  the graphics device.
    485         * @param dataArea  the area in which the data is to be plotted.
    486         * @param plot  the plot.
    487         * @param rendererIndex  the renderer index.
    488         * @param info  collects chart rendering information for return to caller.
    489         *
    490         * @return The renderer state.
    491         */
    492        public CategoryItemRendererState initialise(Graphics2D g2,
    493                                                    Rectangle2D dataArea,
    494                                                    CategoryPlot plot,
    495                                                    int rendererIndex,
    496                                                    PlotRenderingInfo info) {
    497
    498            CategoryItemRendererState state = super.initialise(g2, dataArea, plot,
    499                    rendererIndex, info);
    500
    501            // get the clipping values...
    502            ValueAxis rangeAxis = plot.getRangeAxisForDataset(rendererIndex);
    503            this.lowerClip = rangeAxis.getRange().getLowerBound();
    504            this.upperClip = rangeAxis.getRange().getUpperBound();
    505
    506            // calculate the bar width
    507            calculateBarWidth(plot, dataArea, rendererIndex, state);
    508
    509            return state;
    510
    511        }
    512
    513        /**
    514         * Calculates the bar width and stores it in the renderer state.
    515         *
    516         * @param plot  the plot.
    517         * @param dataArea  the data area.
    518         * @param rendererIndex  the renderer index.
    519         * @param state  the renderer state.
    520         */
    521        protected void calculateBarWidth(CategoryPlot plot,
    522                                         Rectangle2D dataArea,
    523                                         int rendererIndex,
    524                                         CategoryItemRendererState state) {
    525
    526            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    527            CategoryDataset dataset = plot.getDataset(rendererIndex);
    528            if (dataset != null) {
    529                int columns = dataset.getColumnCount();
    530                int rows = dataset.getRowCount();
    531                double space = 0.0;
    532                PlotOrientation orientation = plot.getOrientation();
    533                if (orientation == PlotOrientation.HORIZONTAL) {
    534                    space = dataArea.getHeight();
    535                }
    536                else if (orientation == PlotOrientation.VERTICAL) {
    537                    space = dataArea.getWidth();
    538                }
    539                double maxWidth = space * getMaximumBarWidth();
    540                double categoryMargin = 0.0;
    541                double currentItemMargin = 0.0;
    542                if (columns > 1) {
    543                    categoryMargin = domainAxis.getCategoryMargin();
    544                }
    545                if (rows > 1) {
    546                    currentItemMargin = getItemMargin();
    547                }
    548                double used = space * (1 - domainAxis.getLowerMargin()
    549                                         - domainAxis.getUpperMargin()
    550                                         - categoryMargin - currentItemMargin);
    551                if ((rows * columns) > 0) {
    552                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
    553                }
    554                else {
    555                    state.setBarWidth(Math.min(used, maxWidth));
    556                }
    557            }
    558        }
    559
    560        /**
    561         * Calculates the coordinate of the first "side" of a bar.  This will be
    562         * the minimum x-coordinate for a vertical bar, and the minimum
    563         * y-coordinate for a horizontal bar.
    564         *
    565         * @param plot  the plot.
    566         * @param orientation  the plot orientation.
    567         * @param dataArea  the data area.
    568         * @param domainAxis  the domain axis.
    569         * @param state  the renderer state (has the bar width precalculated).
    570         * @param row  the row index.
    571         * @param column  the column index.
    572         *
    573         * @return The coordinate.
    574         */
    575        protected double calculateBarW0(CategoryPlot plot,
    576                                        PlotOrientation orientation,
    577                                        Rectangle2D dataArea,
    578                                        CategoryAxis domainAxis,
    579                                        CategoryItemRendererState state,
    580                                        int row,
    581                                        int column) {
    582            // calculate bar width...
    583            double space = 0.0;
    584            if (orientation == PlotOrientation.HORIZONTAL) {
    585                space = dataArea.getHeight();
    586            }
    587            else {
    588                space = dataArea.getWidth();
    589            }
    590            double barW0 = domainAxis.getCategoryStart(column, getColumnCount(),
    591                    dataArea, plot.getDomainAxisEdge());
    592            int seriesCount = getRowCount();
    593            int categoryCount = getColumnCount();
    594            if (seriesCount > 1) {
    595                double seriesGap = space * getItemMargin()
    596                                   / (categoryCount * (seriesCount - 1));
    597                double seriesW = calculateSeriesWidth(space, domainAxis,
    598                        categoryCount, seriesCount);
    599                barW0 = barW0 + row * (seriesW + seriesGap)
    600                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
    601            }
    602            else {
    603                barW0 = domainAxis.getCategoryMiddle(column, getColumnCount(),
    604                        dataArea, plot.getDomainAxisEdge()) - state.getBarWidth()
    605                        / 2.0;
    606            }
    607            return barW0;
    608        }
    609
    610        /**
    611         * Calculates the coordinates for the length of a single bar.
    612         *
    613         * @param value  the value represented by the bar.
    614         *
    615         * @return The coordinates for each end of the bar (or <code>null</code> if
    616         *         the bar is not visible for the current axis range).
    617         */
    618        protected double[] calculateBarL0L1(double value) {
    619            double lclip = getLowerClip();
    620            double uclip = getUpperClip();
    621            double barLow = Math.min(this.base, value);
    622            double barHigh = Math.max(this.base, value);
    623            if (barHigh < lclip) {  // bar is not visible
    624                return null;
    625            }
    626            if (barLow > uclip) {   // bar is not visible
    627                return null;
    628            }
    629            barLow = Math.max(barLow, lclip);
    630            barHigh = Math.min(barHigh, uclip);
    631            return new double[] {barLow, barHigh};
    632        }
    633
    634        /**
    635         * Returns the range of values the renderer requires to display all the
    636         * items from the specified dataset.  This takes into account the range
    637         * of values in the dataset, plus the flag that determines whether or not
    638         * the base value for the bars should be included in the range.
    639         *
    640         * @param dataset  the dataset (<code>null</code> permitted).
    641         *
    642         * @return The range (or <code>null</code> if the dataset is
    643         *         <code>null</code> or empty).
    644         */
    645        public Range findRangeBounds(CategoryDataset dataset) {
    646            Range result = DatasetUtilities.findRangeBounds(dataset);
    647            if (result != null) {
    648                if (this.includeBaseInRange) {
    649                    result = Range.expandToInclude(result, this.base);
    650                }
    651            }
    652            return result;
    653        }
    654
    655        /**
    656         * Returns a legend item for a series.
    657         *
    658         * @param datasetIndex  the dataset index (zero-based).
    659         * @param series  the series index (zero-based).
    660         *
    661         * @return The legend item (possibly <code>null</code>).
    662         */
    663        public LegendItem getLegendItem(int datasetIndex, int series) {
    664
    665            CategoryPlot cp = getPlot();
    666            if (cp == null) {
    667                return null;
    668            }
    669
    670            // check that a legend item needs to be displayed...
    671            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
    672                return null;
    673            }
    674
    675            CategoryDataset dataset = cp.getDataset(datasetIndex);
    676            String label = getLegendItemLabelGenerator().generateLabel(dataset,
    677                    series);
    678            String description = label;
    679            String toolTipText = null;
    680            if (getLegendItemToolTipGenerator() != null) {
    681                toolTipText = getLegendItemToolTipGenerator().generateLabel(
    682                        dataset, series);
    683            }
    684            String urlText = null;
    685            if (getLegendItemURLGenerator() != null) {
    686                urlText = getLegendItemURLGenerator().generateLabel(dataset,
    687                        series);
    688            }
    689            Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
    690            Paint paint = lookupSeriesPaint(series);
    691            Paint outlinePaint = lookupSeriesOutlinePaint(series);
    692            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
    693
    694            LegendItem result = new LegendItem(label, description, toolTipText,
    695                    urlText, true, shape, true, paint, isDrawBarOutline(),
    696                    outlinePaint, outlineStroke, false, new Line2D.Float(),
    697                    new BasicStroke(1.0f), Color.black);
    698            result.setDataset(dataset);
    699            result.setDatasetIndex(datasetIndex);
    700            result.setSeriesKey(dataset.getRowKey(series));
    701            result.setSeriesIndex(series);
    702            if (this.gradientPaintTransformer != null) {
    703                result.setFillPaintTransformer(this.gradientPaintTransformer);
    704            }
    705            return result;
    706        }
    707
    708        /**
    709         * Draws the bar for a single (series, category) data item.
    710         *
    711         * @param g2  the graphics device.
    712         * @param state  the renderer state.
    713         * @param dataArea  the data area.
    714         * @param plot  the plot.
    715         * @param domainAxis  the domain axis.
    716         * @param rangeAxis  the range axis.
    717         * @param dataset  the dataset.
    718         * @param row  the row index (zero-based).
    719         * @param column  the column index (zero-based).
    720         * @param pass  the pass index.
    721         */
    722        public void drawItem(Graphics2D g2,
    723                             CategoryItemRendererState state,
    724                             Rectangle2D dataArea,
    725                             CategoryPlot plot,
    726                             CategoryAxis domainAxis,
    727                             ValueAxis rangeAxis,
    728                             CategoryDataset dataset,
    729                             int row,
    730                             int column,
    731                             int pass) {
    732
    733            // nothing is drawn for null values...
    734            Number dataValue = dataset.getValue(row, column);
    735            if (dataValue == null) {
    736                return;
    737            }
    738
    739            double value = dataValue.doubleValue();
    740            PlotOrientation orientation = plot.getOrientation();
    741            double barW0 = calculateBarW0(plot, orientation, dataArea, domainAxis,
    742                    state, row, column);
    743            double[] barL0L1 = calculateBarL0L1(value);
    744            if (barL0L1 == null) {
    745                return;  // the bar is not visible
    746            }
    747
    748            RectangleEdge edge = plot.getRangeAxisEdge();
    749            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
    750            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
    751
    752            // in the following code, barL0 is (in Java2D coordinates) the LEFT
    753            // end of the bar for a horizontal bar chart, and the TOP end of the
    754            // bar for a vertical bar chart.  Whether this is the BASE of the bar
    755            // or not depends also on (a) whether the data value is 'negative'
    756            // relative to the base value and (b) whether or not the range axis is
    757            // inverted.  This only matters if/when we apply the minimumBarLength
    758            // attribute, because we should extend the non-base end of the bar
    759            boolean positive = (value >= this.base);
    760            boolean inverted = rangeAxis.isInverted();
    761            double barL0 = Math.min(transL0, transL1);
    762            double barLength = Math.abs(transL1 - transL0);
    763            double barLengthAdj = 0.0;
    764            if (barLength > 0.0 && barLength < getMinimumBarLength()) {
    765                barLengthAdj = getMinimumBarLength() - barLength;
    766            }
    767            double barL0Adj = 0.0;
    768            if (orientation == PlotOrientation.HORIZONTAL) {
    769                if (positive && inverted || !positive && !inverted) {
    770                    barL0Adj = barLengthAdj;
    771                }
    772            }
    773            else {
    774                if (positive && !inverted || !positive && inverted) {
    775                    barL0Adj = barLengthAdj;
    776                }
    777            }
    778
    779            // draw the bar...
    780            Rectangle2D bar = null;
    781            if (orientation == PlotOrientation.HORIZONTAL) {
    782                bar = new Rectangle2D.Double(barL0 - barL0Adj, barW0,
    783                        barLength + barLengthAdj, state.getBarWidth());
    784            }
    785            else {
    786                bar = new Rectangle2D.Double(barW0, barL0 - barL0Adj,
    787                        state.getBarWidth(), barLength + barLengthAdj);
    788            }
    789            Paint itemPaint = getItemPaint(row, column);
    790            GradientPaintTransformer t = getGradientPaintTransformer();
    791            if (t != null && itemPaint instanceof GradientPaint) {
    792                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    793            }
    794            g2.setPaint(itemPaint);
    795            g2.fill(bar);
    796
    797            // draw the outline...
    798            if (isDrawBarOutline()
    799                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    800                Stroke stroke = getItemOutlineStroke(row, column);
    801                Paint paint = getItemOutlinePaint(row, column);
    802                if (stroke != null && paint != null) {
    803                    g2.setStroke(stroke);
    804                    g2.setPaint(paint);
    805                    g2.draw(bar);
    806                }
    807            }
    808
    809            CategoryItemLabelGenerator generator
    810                = getItemLabelGenerator(row, column);
    811            if (generator != null && isItemLabelVisible(row, column)) {
    812                drawItemLabel(g2, dataset, row, column, plot, generator, bar,
    813                        (value < 0.0));
    814            }
    815
    816            // add an item entity, if this information is being collected
    817            EntityCollection entities = state.getEntityCollection();
    818            if (entities != null) {
    819                addItemEntity(entities, dataset, row, column, bar);
    820            }
    821
    822        }
    823
    824        /**
    825         * Calculates the available space for each series.
    826         *
    827         * @param space  the space along the entire axis (in Java2D units).
    828         * @param axis  the category axis.
    829         * @param categories  the number of categories.
    830         * @param series  the number of series.
    831         *
    832         * @return The width of one series.
    833         */
    834        protected double calculateSeriesWidth(double space, CategoryAxis axis,
    835                                              int categories, int series) {
    836            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
    837                                - axis.getUpperMargin();
    838            if (categories > 1) {
    839                factor = factor - axis.getCategoryMargin();
    840            }
    841            return (space * factor) / (categories * series);
    842        }
    843
    844        /**
    845         * Draws an item label.  This method is overridden so that the bar can be
    846         * used to calculate the label anchor point.
    847         *
    848         * @param g2  the graphics device.
    849         * @param data  the dataset.
    850         * @param row  the row.
    851         * @param column  the column.
    852         * @param plot  the plot.
    853         * @param generator  the label generator.
    854         * @param bar  the bar.
    855         * @param negative  a flag indicating a negative value.
    856         */
    857        protected void drawItemLabel(Graphics2D g2,
    858                                     CategoryDataset data,
    859                                     int row,
    860                                     int column,
    861                                     CategoryPlot plot,
    862                                     CategoryItemLabelGenerator generator,
    863                                     Rectangle2D bar,
    864                                     boolean negative) {
    865
    866            String label = generator.generateLabel(data, row, column);
    867            if (label == null) {
    868                return;  // nothing to do
    869            }
    870
    871            Font labelFont = getItemLabelFont(row, column);
    872            g2.setFont(labelFont);
    873            Paint paint = getItemLabelPaint(row, column);
    874            g2.setPaint(paint);
    875
    876            // find out where to place the label...
    877            ItemLabelPosition position = null;
    878            if (!negative) {
    879                position = getPositiveItemLabelPosition(row, column);
    880            }
    881            else {
    882                position = getNegativeItemLabelPosition(row, column);
    883            }
    884
    885            // work out the label anchor point...
    886            Point2D anchorPoint = calculateLabelAnchorPoint(
    887                    position.getItemLabelAnchor(), bar, plot.getOrientation());
    888
    889            if (isInternalAnchor(position.getItemLabelAnchor())) {
    890                Shape bounds = TextUtilities.calculateRotatedStringBounds(label,
    891                        g2, (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    892                        position.getTextAnchor(), position.getAngle(),
    893                        position.getRotationAnchor());
    894
    895                if (bounds != null) {
    896                    if (!bar.contains(bounds.getBounds2D())) {
    897                        if (!negative) {
    898                            position = getPositiveItemLabelPositionFallback();
    899                        }
    900                        else {
    901                            position = getNegativeItemLabelPositionFallback();
    902                        }
    903                        if (position != null) {
    904                            anchorPoint = calculateLabelAnchorPoint(
    905                                    position.getItemLabelAnchor(), bar,
    906                                    plot.getOrientation());
    907                        }
    908                    }
    909                }
    910
    911            }
    912
    913            if (position != null) {
    914                TextUtilities.drawRotatedString(label, g2,
    915                        (float) anchorPoint.getX(), (float) anchorPoint.getY(),
    916                        position.getTextAnchor(), position.getAngle(),
    917                        position.getRotationAnchor());
    918            }
    919        }
    920
    921        /**
    922         * Calculates the item label anchor point.
    923         *
    924         * @param anchor  the anchor.
    925         * @param bar  the bar.
    926         * @param orientation  the plot orientation.
    927         *
    928         * @return The anchor point.
    929         */
    930        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    931                                                  Rectangle2D bar,
    932                                                  PlotOrientation orientation) {
    933
    934            Point2D result = null;
    935            double offset = getItemLabelAnchorOffset();
    936            double x0 = bar.getX() - offset;
    937            double x1 = bar.getX();
    938            double x2 = bar.getX() + offset;
    939            double x3 = bar.getCenterX();
    940            double x4 = bar.getMaxX() - offset;
    941            double x5 = bar.getMaxX();
    942            double x6 = bar.getMaxX() + offset;
    943
    944            double y0 = bar.getMaxY() + offset;
    945            double y1 = bar.getMaxY();
    946            double y2 = bar.getMaxY() - offset;
    947            double y3 = bar.getCenterY();
    948            double y4 = bar.getMinY() + offset;
    949            double y5 = bar.getMinY();
    950            double y6 = bar.getMinY() - offset;
    951
    952            if (anchor == ItemLabelAnchor.CENTER) {
    953                result = new Point2D.Double(x3, y3);
    954            }
    955            else if (anchor == ItemLabelAnchor.INSIDE1) {
    956                result = new Point2D.Double(x4, y4);
    957            }
    958            else if (anchor == ItemLabelAnchor.INSIDE2) {
    959                result = new Point2D.Double(x4, y4);
    960            }
    961            else if (anchor == ItemLabelAnchor.INSIDE3) {
    962                result = new Point2D.Double(x4, y3);
    963            }
    964            else if (anchor == ItemLabelAnchor.INSIDE4) {
    965                result = new Point2D.Double(x4, y2);
    966            }
    967            else if (anchor == ItemLabelAnchor.INSIDE5) {
    968                result = new Point2D.Double(x4, y2);
    969            }
    970            else if (anchor == ItemLabelAnchor.INSIDE6) {
    971                result = new Point2D.Double(x3, y2);
    972            }
    973            else if (anchor == ItemLabelAnchor.INSIDE7) {
    974                result = new Point2D.Double(x2, y2);
    975            }
    976            else if (anchor == ItemLabelAnchor.INSIDE8) {
    977                result = new Point2D.Double(x2, y2);
    978            }
    979            else if (anchor == ItemLabelAnchor.INSIDE9) {
    980                result = new Point2D.Double(x2, y3);
    981            }
    982            else if (anchor == ItemLabelAnchor.INSIDE10) {
    983                result = new Point2D.Double(x2, y4);
    984            }
    985            else if (anchor == ItemLabelAnchor.INSIDE11) {
    986                result = new Point2D.Double(x2, y4);
    987            }
    988            else if (anchor == ItemLabelAnchor.INSIDE12) {
    989                result = new Point2D.Double(x3, y4);
    990            }
    991            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    992                result = new Point2D.Double(x5, y6);
    993            }
    994            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    995                result = new Point2D.Double(x6, y5);
    996            }
    997            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    998                result = new Point2D.Double(x6, y3);
    999            }
    1000            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    1001                result = new Point2D.Double(x6, y1);
    1002            }
    1003            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    1004                result = new Point2D.Double(x5, y0);
    1005            }
    1006            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    1007                result = new Point2D.Double(x3, y0);
    1008            }
    1009            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    1010                result = new Point2D.Double(x1, y0);
    1011            }
    1012            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    1013                result = new Point2D.Double(x0, y1);
    1014            }
    1015            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    1016                result = new Point2D.Double(x0, y3);
    1017            }
    1018            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    1019                result = new Point2D.Double(x0, y5);
    1020            }
    1021            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    1022                result = new Point2D.Double(x1, y6);
    1023            }
    1024            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    1025                result = new Point2D.Double(x3, y6);
    1026            }
    1027
    1028            return result;
    1029
    1030        }
    1031
    1032        /**
    1033         * Returns <code>true</code> if the specified anchor point is inside a bar.
    1034         *
    1035         * @param anchor  the anchor point.
    1036         *
    1037         * @return A boolean.
    1038         */
    1039        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    1040            return anchor == ItemLabelAnchor.CENTER
    1041                   || anchor == ItemLabelAnchor.INSIDE1
    1042                   || anchor == ItemLabelAnchor.INSIDE2
    1043                   || anchor == ItemLabelAnchor.INSIDE3
    1044                   || anchor == ItemLabelAnchor.INSIDE4
    1045                   || anchor == ItemLabelAnchor.INSIDE5
    1046                   || anchor == ItemLabelAnchor.INSIDE6
    1047                   || anchor == ItemLabelAnchor.INSIDE7
    1048                   || anchor == ItemLabelAnchor.INSIDE8
    1049                   || anchor == ItemLabelAnchor.INSIDE9
    1050                   || anchor == ItemLabelAnchor.INSIDE10
    1051                   || anchor == ItemLabelAnchor.INSIDE11
    1052                   || anchor == ItemLabelAnchor.INSIDE12;
    1053        }
    1054
    1055        /**
    1056         * Tests this instance for equality with an arbitrary object.
    1057         *
    1058         * @param obj  the object (<code>null</code> permitted).
    1059         *
    1060         * @return A boolean.
    1061         */
    1062        public boolean equals(Object obj) {
    1063
    1064            if (obj == this) {
    1065                return true;
    1066            }
    1067            if (!(obj instanceof BarRenderer)) {
    1068                return false;
    1069            }
    1070            if (!super.equals(obj)) {
    1071                return false;
    1072            }
    1073            BarRenderer that = (BarRenderer) obj;
    1074            if (this.base != that.base) {
    1075                return false;
    1076            }
    1077            if (this.itemMargin != that.itemMargin) {
    1078                return false;
    1079            }
    1080            if (this.drawBarOutline != that.drawBarOutline) {
    1081                return false;
    1082            }
    1083            if (this.maximumBarWidth != that.maximumBarWidth) {
    1084                return false;
    1085            }
    1086            if (this.minimumBarLength != that.minimumBarLength) {
    1087                return false;
    1088            }
    1089            if (!ObjectUtilities.equal(this.gradientPaintTransformer,
    1090                    that.gradientPaintTransformer)) {
    1091                return false;
    1092            }
    1093            if (!ObjectUtilities.equal(this.positiveItemLabelPositionFallback,
    1094                that.positiveItemLabelPositionFallback)) {
    1095                return false;
    1096            }
    1097            if (!ObjectUtilities.equal(this.negativeItemLabelPositionFallback,
    1098                that.negativeItemLabelPositionFallback)) {
    1099                return false;
    1100            }
    1101            return true;
    1102
    1103        }
    1104
    1105    }