    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, by Object Refinery Limited.
    031     *
    032     * Original Author:  David Gilbert (for Object Refinery Limited);
    033     * Contributor(s):   Christian W. Zuckschwerdt;
    034     *
    035     * $Id: BarRenderer.java,v 1.13.2.7 2005/12/01 11:38:58 mungady Exp $
    036     *
    037     * Changes
    038     * -------
    039     * 14-Mar-2002 : Version 1 (DG);
    040     * 23-May-2002 : Added tooltip generator to renderer (DG);
    041     * 29-May-2002 : Moved tooltip generator to abstract super-class (DG);
    042     * 25-Jun-2002 : Changed constructor to protected and removed redundant
    043     *               code (DG);
    044     * 26-Jun-2002 : Added axis to initialise method, and record upper and lower
    045     *               clip values (DG);
    046     * 24-Sep-2002 : Added getLegendItem() method (DG);
    047     * 09-Oct-2002 : Modified constructor to include URL generator (DG);
    048     * 05-Nov-2002 : Base dataset is now TableDataset not CategoryDataset (DG);
    049     * 10-Jan-2003 : Moved get/setItemMargin() method up from subclasses (DG);
    050     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
    051     * 25-Mar-2003 : Implemented Serializable (DG);
    052     * 01-May-2003 : Modified clipping to allow for dual axes and datasets (DG);
    053     * 12-May-2003 : Merged horizontal and vertical bar renderers (DG);
    054     * 12-Jun-2003 : Updates for item labels (DG);
    055     * 30-Jul-2003 : Modified entity constructor (CZ);
    056     * 02-Sep-2003 : Changed initialise method to fix bug 790407 (DG);
    057     * 16-Sep-2003 : Changed ChartRenderingInfo --> PlotRenderingInfo (DG);
    058     * 07-Oct-2003 : Added renderer state (DG);
    059     * 27-Oct-2003 : Merged drawHorizontalItem() and drawVerticalItem()
    060     *               methods (DG);
    061     * 28-Oct-2003 : Added support for gradient paint on bars (DG);
    062     * 14-Nov-2003 : Added 'maxBarWidth' attribute (DG);
    063     * 10-Feb-2004 : Small changes inside drawItem() method to ease cut-and-paste
    064     *               overriding (DG);
    065     * 19-Mar-2004 : Fixed bug introduced with separation of tool tip and item
    066     *               label generators.  Fixed equals() method (DG);
    067     * 11-May-2004 : Fix for null pointer exception (bug id 951127) (DG);
    068     * 05-Nov-2004 : Modified drawItem() signature (DG);
    069     * 26-Jan-2005 : Provided override for getLegendItem() method (DG);
    070     * 20-Apr-2005 : Generate legend labels, tooltips and URLs (DG);
    071     * 18-May-2005 : Added configurable base value (DG);
    072     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
    073     * 01-Dec-2005 : Update legend item to use/not use outline (DG);
    074     *
    075     */
    076
    077    package org.jfree.chart.renderer.category;
    078
    079    import java.awt.BasicStroke;
    080    import java.awt.Color;
    081    import java.awt.Font;
    082    import java.awt.GradientPaint;
    083    import java.awt.Graphics2D;
    084    import java.awt.Paint;
    085    import java.awt.Shape;
    086    import java.awt.Stroke;
    087    import java.awt.geom.Line2D;
    088    import java.awt.geom.Point2D;
    089    import java.awt.geom.Rectangle2D;
    090    import java.io.Serializable;
    091
    092    import org.jfree.chart.LegendItem;
    093    import org.jfree.chart.axis.CategoryAxis;
    094    import org.jfree.chart.axis.ValueAxis;
    095    import org.jfree.chart.entity.EntityCollection;
    096    import org.jfree.chart.event.RendererChangeEvent;
    097    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    098    import org.jfree.chart.labels.ItemLabelAnchor;
    099    import org.jfree.chart.labels.ItemLabelPosition;
    100    import org.jfree.chart.plot.CategoryPlot;
    101    import org.jfree.chart.plot.PlotOrientation;
    102    import org.jfree.chart.plot.PlotRenderingInfo;
    103    import org.jfree.data.category.CategoryDataset;
    104    import org.jfree.text.TextUtilities;
    105    import org.jfree.ui.GradientPaintTransformer;
    106    import org.jfree.ui.RectangleEdge;
    107    import org.jfree.ui.StandardGradientPaintTransformer;
    108    import org.jfree.util.ObjectUtilities;
    109    import org.jfree.util.PublicCloneable;
    110
    111    /**
    112     * A {@link CategoryItemRenderer} that draws individual data items as bars.
    113     */
    114    public class BarRenderer extends AbstractCategoryItemRenderer
    115                             implements Cloneable, PublicCloneable, Serializable {
    116
    117        /** For serialization. */
    118        private static final long serialVersionUID = 6000649414965887481L;
    119
    120        /** The default item margin percentage. */
    121        public static final double DEFAULT_ITEM_MARGIN = 0.20;
    122
    123        /**
    124         * Constant that controls the minimum width before a bar has an outline
    125         * drawn.
    126         */
    127        public static final double BAR_OUTLINE_WIDTH_THRESHOLD = 3.0;
    128
    129        /** The margin between items (bars) within a category. */
    130        private double itemMargin;
    131
    132        /** A flag that controls whether or not bar outlines are drawn. */
    133        private boolean drawBarOutline;
    134
    135        /** The maximum bar width as a percentage of the available space. */
    136        private double maximumBarWidth;
    137
    138        /** The minimum bar length (in Java2D units). */
    139        private double minimumBarLength;
    140
    141        /**
    142         * An optional class used to transform gradient paint objects to fit each
    143         * bar.
    144         */
    145        private GradientPaintTransformer gradientPaintTransformer;
    146
    147        /**
    148         * The fallback position if a positive item label doesn't fit inside the
    149         * bar.
    150         */
    151        private ItemLabelPosition positiveItemLabelPositionFallback;
    152
    153        /**
    154         * The fallback position if a negative item label doesn't fit inside the
    155         * bar.
    156         */
    157        private ItemLabelPosition negativeItemLabelPositionFallback;
    158
    159        /** The upper clip (axis) value for the axis. */
    160        private double upperClip;
    161        // TODO:  this needs to move into the renderer state
    162
    163        /** The lower clip (axis) value for the axis. */
    164        private double lowerClip;
    165        // TODO:  this needs to move into the renderer state
    166
    167        private double base;
    168
    169        /**
    170         * Creates a new bar renderer with default settings.
    171         */
    172        public BarRenderer() {
    173            super();
    174            this.base = 0;
    175            this.itemMargin = DEFAULT_ITEM_MARGIN;
    176            this.drawBarOutline = true;
    177            this.maximumBarWidth = 1.0;
    178                // 100 percent, so it will not apply unless changed
    179            this.positiveItemLabelPositionFallback = null;
    180            this.negativeItemLabelPositionFallback = null;
    181            this.gradientPaintTransformer = new StandardGradientPaintTransformer();
    182            this.minimumBarLength = 0.0;
    183        }
    184
    185        /**
    186         * Returns the base value for the bars.
    187         *
    188         * @return The base value for the bars.
    189         */
    190        public double getBase() {
    191            return this.base;
    192        }
    193
    194        /**
    195         * Sets the base value for the bars and sends a {@link RendererChangeEvent}
    196         * to all registered listeners.
    197         *
    198         * @param base  the new base value.
    199         */
    200        public void setBase(double base) {
    201            this.base = base;
    202            notifyListeners(new RendererChangeEvent(this));
    203        }
    204
    205        /**
    206         * Returns the item margin as a percentage of the available space for all
    207         * bars.
    208         *
    209         * @return The margin percentage (where 0.10 is ten percent).
    210         */
    211        public double getItemMargin() {
    212            return this.itemMargin;
    213        }
    214
    215        /**
    216         * Sets the item margin and sends a {@link RendererChangeEvent} to all
    217         * registered listeners.  The value is expressed as a percentage of the
    218         * available width for plotting all the bars, with the resulting amount to
    219         * be distributed between all the bars evenly.
    220         *
    221         * @param percent  the margin (where 0.10 is ten percent).
    222         */
    223        public void setItemMargin(double percent) {
    224            this.itemMargin = percent;
    225            notifyListeners(new RendererChangeEvent(this));
    226        }
    227
    228        /**
    229         * Returns a flag that controls whether or not bar outlines are drawn.
    230         *
    231         * @return A boolean.
    232         */
    233        public boolean isDrawBarOutline() {
    234            return this.drawBarOutline;
    235        }
    236
    237        /**
    238         * Sets the flag that controls whether or not bar outlines are drawn and
    239         * sends a {@link RendererChangeEvent} to all registered listeners.
    240         *
    241         * @param draw  the flag.
    242         */
    243        public void setDrawBarOutline(boolean draw) {
    244            this.drawBarOutline = draw;
    245            notifyListeners(new RendererChangeEvent(this));
    246        }
    247
    248        /**
    249         * Returns the maximum bar width, as a percentage of the available drawing
    250         * space.
    251         *
    252         * @return The maximum bar width.
    253         */
    254        public double getMaximumBarWidth() {
    255            return this.maximumBarWidth;
    256        }
    257
    258        /**
    259         * Sets the maximum bar width, which is specified as a percentage of the
    260         * available space for all bars, and sends a {@link RendererChangeEvent} to
    261         * all registered listeners.
    262         *
    263         * @param percent  the percent (where 0.05 is five percent).
    264         */
    265        public void setMaximumBarWidth(double percent) {
    266            this.maximumBarWidth = percent;
    267            notifyListeners(new RendererChangeEvent(this));
    268        }
    269
    270        /**
    271         * Returns the minimum bar length (in Java2D units).
    272         *
    273         * @return The minimum bar length.
    274         */
    275        public double getMinimumBarLength() {
    276            return this.minimumBarLength;
    277        }
    278
    279        /**
    280         * Sets the minimum bar length and sends a {@link RendererChangeEvent} to
    281         * all registered listeners.  The minimum bar length is specified in Java2D
    282         * units, and can be used to prevent bars that represent very small data
    283         * values from disappearing when drawn on the screen.
    284         *
    285         * @param min  the minimum bar length (in Java2D units).
    286         */
    287        public void setMinimumBarLength(double min) {
    288            this.minimumBarLength = min;
    289            notifyListeners(new RendererChangeEvent(this));
    290        }
    291
    292        /**
    293         * Returns the gradient paint transformer (an object used to transform
    294         * gradient paint objects to fit each bar.
    295         *
    296         * @return A transformer (<code>null</code> possible).
    297         */
    298        public GradientPaintTransformer getGradientPaintTransformer() {
    299            return this.gradientPaintTransformer;
    300        }
    301
    302        /**
    303         * Sets the gradient paint transformer and sends a
    304         * {@link RendererChangeEvent} to all registered listeners.
    305         *
    306         * @param transformer  the transformer (<code>null</code> permitted).
    307         */
    308        public void setGradientPaintTransformer(
    309                GradientPaintTransformer transformer) {
    310            this.gradientPaintTransformer = transformer;
    311            notifyListeners(new RendererChangeEvent(this));
    312        }
    313
    314        /**
    315         * Returns the fallback position for positive item labels that don't fit
    316         * within a bar.
    317         *
    318         * @return The fallback position (<code>null</code> possible).
    319         */
    320        public ItemLabelPosition getPositiveItemLabelPositionFallback() {
    321            return this.positiveItemLabelPositionFallback;
    322        }
    323
    324        /**
    325         * Sets the fallback position for positive item labels that don't fit
    326         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    327         * listeners.
    328         *
    329         * @param position  the position (<code>null</code> permitted).
    330         */
    331        public void setPositiveItemLabelPositionFallback(
    332                ItemLabelPosition position) {
    333            this.positiveItemLabelPositionFallback = position;
    334            notifyListeners(new RendererChangeEvent(this));
    335        }
    336
    337        /**
    338         * Returns the fallback position for negative item labels that don't fit
    339         * within a bar.
    340         *
    341         * @return The fallback position (<code>null</code> possible).
    342         */
    343        public ItemLabelPosition getNegativeItemLabelPositionFallback() {
    344            return this.negativeItemLabelPositionFallback;
    345        }
    346
    347        /**
    348         * Sets the fallback position for negative item labels that don't fit
    349         * within a bar, and sends a {@link RendererChangeEvent} to all registered
    350         * listeners.
    351         *
    352         * @param position  the position (<code>null</code> permitted).
    353         */
    354        public void setNegativeItemLabelPositionFallback(
    355                ItemLabelPosition position) {
    356            this.negativeItemLabelPositionFallback = position;
    357            notifyListeners(new RendererChangeEvent(this));
    358        }
    359
    360        /**
    361         * Returns the lower clip value.  This value is recalculated in the
    362         * initialise() method.
    363         *
    364         * @return The value.
    365         */
    366        public double getLowerClip() {
    367            // TODO:  this attribute should be transferred to the renderer state.
    368            return this.lowerClip;
    369        }
    370
    371        /**
    372         * Returns the upper clip value.  This value is recalculated in the
    373         * initialise() method.
    374         *
    375         * @return The value.
    376         */
    377        public double getUpperClip() {
    378            // TODO:  this attribute should be transferred to the renderer state.
    379            return this.upperClip;
    380        }
    381
    382        /**
    383         * Initialises the renderer and returns a state object that will be passed
    384         * to subsequent calls to the drawItem method.  This method gets called
    385         * once at the start of the process of drawing a chart.
    386         *
    387         * @param g2  the graphics device.
    388         * @param dataArea  the area in which the data is to be plotted.
    389         * @param plot  the plot.
    390         * @param rendererIndex  the renderer index.
    391         * @param info  collects chart rendering information for return to caller.
    392         *
    393         * @return The renderer state.
    394         */
    395        public CategoryItemRendererState initialise(Graphics2D g2,
    396                                                    Rectangle2D dataArea,
    397                                                    CategoryPlot plot,
    398                                                    int rendererIndex,
    399                                                    PlotRenderingInfo info) {
    400
    401            CategoryItemRendererState state = super.initialise(
    402                g2, dataArea, plot, rendererIndex, info
    403            );
    404
    405            // get the clipping values...
    406            ValueAxis rangeAxis = getRangeAxis(plot, rendererIndex);
    407            this.lowerClip = rangeAxis.getRange().getLowerBound();
    408            this.upperClip = rangeAxis.getRange().getUpperBound();
    409
    410            // calculate the bar width
    411            calculateBarWidth(plot, dataArea, rendererIndex, state);
    412
    413            return state;
    414
    415        }
    416
    417        /**
    418         * Calculates the bar width and stores it in the renderer state.
    419         *
    420         * @param plot  the plot.
    421         * @param dataArea  the data area.
    422         * @param rendererIndex  the renderer index.
    423         * @param state  the renderer state.
    424         */
    425        protected void calculateBarWidth(CategoryPlot plot,
    426                                         Rectangle2D dataArea,
    427                                         int rendererIndex,
    428                                         CategoryItemRendererState state) {
    429
    430            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    431            CategoryDataset dataset = plot.getDataset(rendererIndex);
    432            if (dataset != null) {
    433                int columns = dataset.getColumnCount();
    434                int rows = dataset.getRowCount();
    435                double space = 0.0;
    436                PlotOrientation orientation = plot.getOrientation();
    437                if (orientation == PlotOrientation.HORIZONTAL) {
    438                    space = dataArea.getHeight();
    439                }
    440                else if (orientation == PlotOrientation.VERTICAL) {
    441                    space = dataArea.getWidth();
    442                }
    443                double maxWidth = space * getMaximumBarWidth();
    444                double categoryMargin = 0.0;
    445                double currentItemMargin = 0.0;
    446                if (columns > 1) {
    447                    categoryMargin = domainAxis.getCategoryMargin();
    448                }
    449                if (rows > 1) {
    450                    currentItemMargin = getItemMargin();
    451                }
    452                double used = space * (1 - domainAxis.getLowerMargin()
    453                                         - domainAxis.getUpperMargin()
    454                                         - categoryMargin - currentItemMargin);
    455                if ((rows * columns) > 0) {
    456                    state.setBarWidth(Math.min(used / (rows * columns), maxWidth));
    457                }
    458                else {
    459                    state.setBarWidth(Math.min(used, maxWidth));
    460                }
    461            }
    462        }
    463
    464        /**
    465         * Calculates the coordinate of the first "side" of a bar.  This will be
    466         * the minimum x-coordinate for a vertical bar, and the minimum
    467         * y-coordinate for a horizontal bar.
    468         *
    469         * @param plot  the plot.
    470         * @param orientation  the plot orientation.
    471         * @param dataArea  the data area.
    472         * @param domainAxis  the domain axis.
    473         * @param state  the renderer state (has the bar width precalculated).
    474         * @param row  the row index.
    475         * @param column  the column index.
    476         *
    477         * @return The coordinate.
    478         */
    479        protected double calculateBarW0(CategoryPlot plot,
    480                                        PlotOrientation orientation,
    481                                        Rectangle2D dataArea,
    482                                        CategoryAxis domainAxis,
    483                                        CategoryItemRendererState state,
    484                                        int row,
    485                                        int column) {
    486            // calculate bar width...
    487            double space = 0.0;
    488            if (orientation == PlotOrientation.HORIZONTAL) {
    489                space = dataArea.getHeight();
    490            }
    491            else {
    492                space = dataArea.getWidth();
    493            }
    494            double barW0 = domainAxis.getCategoryStart(
    495                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
    496            );
    497            int seriesCount = getRowCount();
    498            int categoryCount = getColumnCount();
    499            if (seriesCount > 1) {
    500                double seriesGap = space * getItemMargin()
    501                                   / (categoryCount * (seriesCount - 1));
    502                double seriesW = calculateSeriesWidth(
    503                    space, domainAxis, categoryCount, seriesCount
    504                );
    505                barW0 = barW0 + row * (seriesW + seriesGap)
    506                              + (seriesW / 2.0) - (state.getBarWidth() / 2.0);
    507            }
    508            else {
    509                barW0 = domainAxis.getCategoryMiddle(
    510                    column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
    511                ) - state.getBarWidth() / 2.0;
    512            }
    513            return barW0;
    514        }
    515
    516        /**
    517         * Calculates the coordinates for the length of a single bar.
    518         *
    519         * @param value  the value represented by the bar.
    520         *
    521         * @return The coordinates for each end of the bar (or <code>null</code> if
    522         *         the bar is not visible for the current axis range).
    523         */
    524        protected double[] calculateBarL0L1(double value) {
    525
    526            double lclip = getLowerClip();
    527            double uclip = getUpperClip();
    528            double bb = this.base;
    529            if (uclip <= 0.0) {  // cases 1, 2, 3 and 4
    530                if (value >= uclip) {
    531                    return null; // bar is not visible
    532                }
    533                bb = uclip;
    534                if (value <= lclip) {
    535                    value = lclip;
    536                }
    537            }
    538            else if (lclip <= 0.0) { // cases 5, 6, 7 and 8
    539                if (value >= uclip) {
    540                    value = uclip;
    541                }
    542                else {
    543                    if (value <= lclip) {
    544                        value = lclip;
    545                    }
    546                }
    547            }
    548            else { // cases 9, 10, 11 and 12
    549                if (value <= lclip) {
    550                    return null; // bar is not visible
    551                }
    552                bb = lclip;
    553                if (value >= uclip) {
    554                    value = uclip;
    555                }
    556            }
    557            return new double[] {bb, value};
    558        }
    559
    560        /**
    561         * Returns a legend item for a series.
    562         *
    563         * @param datasetIndex  the dataset index (zero-based).
    564         * @param series  the series index (zero-based).
    565         *
    566         * @return The legend item.
    567         */
    568        public LegendItem getLegendItem(int datasetIndex, int series) {
    569
    570            CategoryPlot cp = getPlot();
    571            if (cp == null) {
    572                return null;
    573            }
    574
    575            CategoryDataset dataset;
    576            dataset = cp.getDataset(datasetIndex);
    577            String label = getLegendItemLabelGenerator().generateLabel(dataset,
    578                    series);
    579            String description = label;
    580            String toolTipText = null;
    581            if (getLegendItemToolTipGenerator() != null) {
    582                toolTipText = getLegendItemToolTipGenerator().generateLabel(
    583                        dataset, series);
    584            }
    585            String urlText = null;
    586            if (getLegendItemURLGenerator() != null) {
    587                urlText = getLegendItemURLGenerator().generateLabel(dataset,
    588                        series);
    589            }
    590            Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
    591            Paint paint = getSeriesPaint(series);
    592            Paint outlinePaint = getSeriesOutlinePaint(series);
    593            Stroke outlineStroke = getSeriesOutlineStroke(series);
    594
    595            return new LegendItem(label, description, toolTipText, urlText,
    596                          true, shape, true, paint,
    597                          isDrawBarOutline(), outlinePaint, outlineStroke,
    598                          false, new Line2D.Float(), new BasicStroke(1.0f),
    599                          Color.BLACK);
    600        }
    601
    602        /**
    603         * Draws the bar for a single (series, category) data item.
    604         *
    605         * @param g2  the graphics device.
    606         * @param state  the renderer state.
    607         * @param dataArea  the data area.
    608         * @param plot  the plot.
    609         * @param domainAxis  the domain axis.
    610         * @param rangeAxis  the range axis.
    611         * @param dataset  the dataset.
    612         * @param row  the row index (zero-based).
    613         * @param column  the column index (zero-based).
    614         * @param pass  the pass index.
    615         */
    616        public void drawItem(Graphics2D g2,
    617                             CategoryItemRendererState state,
    618                             Rectangle2D dataArea,
    619                             CategoryPlot plot,
    620                             CategoryAxis domainAxis,
    621                             ValueAxis rangeAxis,
    622                             CategoryDataset dataset,
    623                             int row,
    624                             int column,
    625                             int pass) {
    626
    627            // nothing is drawn for null values...
    628            Number dataValue = dataset.getValue(row, column);
    629            if (dataValue == null) {
    630                return;
    631            }
    632
    633            double value = dataValue.doubleValue();
    634
    635            PlotOrientation orientation = plot.getOrientation();
    636            double barW0 = calculateBarW0(
    637                plot, orientation, dataArea, domainAxis, state, row, column
    638            );
    639            double[] barL0L1 = calculateBarL0L1(value);
    640            if (barL0L1 == null) {
    641                return;  // the bar is not visible
    642            }
    643
    644            RectangleEdge edge = plot.getRangeAxisEdge();
    645            double transL0 = rangeAxis.valueToJava2D(barL0L1[0], dataArea, edge);
    646            double transL1 = rangeAxis.valueToJava2D(barL0L1[1], dataArea, edge);
    647            double barL0 = Math.min(transL0, transL1);
    648            double barLength = Math.max(
    649                Math.abs(transL1 - transL0), getMinimumBarLength()
    650            );
    651
    652            // draw the bar...
    653            Rectangle2D bar = null;
    654            if (orientation == PlotOrientation.HORIZONTAL) {
    655                bar = new Rectangle2D.Double(
    656                    barL0, barW0, barLength, state.getBarWidth()
    657                );
    658            }
    659            else {
    660                bar = new Rectangle2D.Double(
    661                    barW0, barL0, state.getBarWidth(), barLength
    662                );
    663            }
    664            Paint itemPaint = getItemPaint(row, column);
    665            GradientPaintTransformer t = getGradientPaintTransformer();
    666            if (t != null && itemPaint instanceof GradientPaint) {
    667                itemPaint = t.transform((GradientPaint) itemPaint, bar);
    668            }
    669            g2.setPaint(itemPaint);
    670            g2.fill(bar);
    671
    672            // draw the outline...
    673            if (isDrawBarOutline()
    674                    && state.getBarWidth() > BAR_OUTLINE_WIDTH_THRESHOLD) {
    675                Stroke stroke = getItemOutlineStroke(row, column);
    676                Paint paint = getItemOutlinePaint(row, column);
    677                if (stroke != null && paint != null) {
    678                    g2.setStroke(stroke);
    679                    g2.setPaint(paint);
    680                    g2.draw(bar);
    681                }
    682            }
    683
    684            CategoryItemLabelGenerator generator
    685                = getItemLabelGenerator(row, column);
    686            if (generator != null && isItemLabelVisible(row, column)) {
    687                drawItemLabel(
    688                    g2, dataset, row, column, plot, generator, bar, (value < 0.0)
    689                );
    690            }
    691
    692            // add an item entity, if this information is being collected
    693            EntityCollection entities = state.getEntityCollection();
    694            if (entities != null) {
    695                addItemEntity(entities, dataset, row, column, bar);
    696            }
    697
    698        }
    699
    700        /**
    701         * Calculates the available space for each series.
    702         *
    703         * @param space  the space along the entire axis (in Java2D units).
    704         * @param axis  the category axis.
    705         * @param categories  the number of categories.
    706         * @param series  the number of series.
    707         *
    708         * @return The width of one series.
    709         */
    710        protected double calculateSeriesWidth(double space, CategoryAxis axis,
    711                                              int categories, int series) {
    712            double factor = 1.0 - getItemMargin() - axis.getLowerMargin()
    713                                - axis.getUpperMargin();
    714            if (categories > 1) {
    715                factor = factor - axis.getCategoryMargin();
    716            }
    717            return (space * factor) / (categories * series);
    718        }
    719
    720        /**
    721         * Draws an item label.  This method is overridden so that the bar can be
    722         * used to calculate the label anchor point.
    723         *
    724         * @param g2  the graphics device.
    725         * @param data  the dataset.
    726         * @param row  the row.
    727         * @param column  the column.
    728         * @param plot  the plot.
    729         * @param generator  the label generator.
    730         * @param bar  the bar.
    731         * @param negative  a flag indicating a negative value.
    732         */
    733        protected void drawItemLabel(Graphics2D g2,
    734                                     CategoryDataset data,
    735                                     int row,
    736                                     int column,
    737                                     CategoryPlot plot,
    738                                     CategoryItemLabelGenerator generator,
    739                                     Rectangle2D bar,
    740                                     boolean negative) {
    741
    742            String label = generator.generateLabel(data, row, column);
    743            if (label == null) {
    744                return;  // nothing to do
    745            }
    746
    747            Font labelFont = getItemLabelFont(row, column);
    748            g2.setFont(labelFont);
    749            Paint paint = getItemLabelPaint(row, column);
    750            g2.setPaint(paint);
    751
    752            // find out where to place the label...
    753            ItemLabelPosition position = null;
    754            if (!negative) {
    755                position = getPositiveItemLabelPosition(row, column);
    756            }
    757            else {
    758                position = getNegativeItemLabelPosition(row, column);
    759            }
    760
    761            // work out the label anchor point...
    762            Point2D anchorPoint = calculateLabelAnchorPoint(
    763                position.getItemLabelAnchor(), bar, plot.getOrientation()
    764            );
    765
    766            if (isInternalAnchor(position.getItemLabelAnchor())) {
    767                Shape bounds = TextUtilities.calculateRotatedStringBounds(
    768                    label, g2,
    769                    (float) anchorPoint.getX(),
    770                    (float) anchorPoint.getY(),
    771                    position.getTextAnchor(),
    772                    position.getAngle(),
    773                    position.getRotationAnchor()
    774                );
    775
    776                if (bounds != null) {
    777                    if (!bar.contains(bounds.getBounds2D())) {
    778                        if (!negative) {
    779                            position = getPositiveItemLabelPositionFallback();
    780                        }
    781                        else {
    782                            position = getNegativeItemLabelPositionFallback();
    783                        }
    784                        if (position != null) {
    785                            anchorPoint = calculateLabelAnchorPoint(
    786                                position.getItemLabelAnchor(), bar,
    787                                plot.getOrientation()
    788                            );
    789                        }
    790                    }
    791                }
    792
    793            }
    794
    795            if (position != null) {
    796                TextUtilities.drawRotatedString(
    797                    label, g2, (float) anchorPoint.getX(),
    798                    (float) anchorPoint.getY(),
    799                    position.getTextAnchor(), position.getAngle(),
    800                    position.getRotationAnchor()
    801                );
    802            }
    803        }
    804
    805        /**
    806         * Calculates the item label anchor point.
    807         *
    808         * @param anchor  the anchor.
    809         * @param bar  the bar.
    810         * @param orientation  the plot orientation.
    811         *
    812         * @return The anchor point.
    813         */
    814        private Point2D calculateLabelAnchorPoint(ItemLabelAnchor anchor,
    815                                                  Rectangle2D bar,
    816                                                  PlotOrientation orientation) {
    817
    818            Point2D result = null;
    819            double offset = getItemLabelAnchorOffset();
    820            double x0 = bar.getX() - offset;
    821            double x1 = bar.getX();
    822            double x2 = bar.getX() + offset;
    823            double x3 = bar.getCenterX();
    824            double x4 = bar.getMaxX() - offset;
    825            double x5 = bar.getMaxX();
    826            double x6 = bar.getMaxX() + offset;
    827
    828            double y0 = bar.getMaxY() + offset;
    829            double y1 = bar.getMaxY();
    830            double y2 = bar.getMaxY() - offset;
    831            double y3 = bar.getCenterY();
    832            double y4 = bar.getMinY() + offset;
    833            double y5 = bar.getMinY();
    834            double y6 = bar.getMinY() - offset;
    835
    836            if (anchor == ItemLabelAnchor.CENTER) {
    837                result = new Point2D.Double(x3, y3);
    838            }
    839            else if (anchor == ItemLabelAnchor.INSIDE1) {
    840                result = new Point2D.Double(x4, y4);
    841            }
    842            else if (anchor == ItemLabelAnchor.INSIDE2) {
    843                result = new Point2D.Double(x4, y4);
    844            }
    845            else if (anchor == ItemLabelAnchor.INSIDE3) {
    846                result = new Point2D.Double(x4, y3);
    847            }
    848            else if (anchor == ItemLabelAnchor.INSIDE4) {
    849                result = new Point2D.Double(x4, y2);
    850            }
    851            else if (anchor == ItemLabelAnchor.INSIDE5) {
    852                result = new Point2D.Double(x4, y2);
    853            }
    854            else if (anchor == ItemLabelAnchor.INSIDE6) {
    855                result = new Point2D.Double(x3, y2);
    856            }
    857            else if (anchor == ItemLabelAnchor.INSIDE7) {
    858                result = new Point2D.Double(x2, y2);
    859            }
    860            else if (anchor == ItemLabelAnchor.INSIDE8) {
    861                result = new Point2D.Double(x2, y2);
    862            }
    863            else if (anchor == ItemLabelAnchor.INSIDE9) {
    864                result = new Point2D.Double(x2, y3);
    865            }
    866            else if (anchor == ItemLabelAnchor.INSIDE10) {
    867                result = new Point2D.Double(x2, y4);
    868            }
    869            else if (anchor == ItemLabelAnchor.INSIDE11) {
    870                result = new Point2D.Double(x2, y4);
    871            }
    872            else if (anchor == ItemLabelAnchor.INSIDE12) {
    873                result = new Point2D.Double(x3, y4);
    874            }
    875            else if (anchor == ItemLabelAnchor.OUTSIDE1) {
    876                result = new Point2D.Double(x5, y6);
    877            }
    878            else if (anchor == ItemLabelAnchor.OUTSIDE2) {
    879                result = new Point2D.Double(x6, y5);
    880            }
    881            else if (anchor == ItemLabelAnchor.OUTSIDE3) {
    882                result = new Point2D.Double(x6, y3);
    883            }
    884            else if (anchor == ItemLabelAnchor.OUTSIDE4) {
    885                result = new Point2D.Double(x6, y1);
    886            }
    887            else if (anchor == ItemLabelAnchor.OUTSIDE5) {
    888                result = new Point2D.Double(x5, y0);
    889            }
    890            else if (anchor == ItemLabelAnchor.OUTSIDE6) {
    891                result = new Point2D.Double(x3, y0);
    892            }
    893            else if (anchor == ItemLabelAnchor.OUTSIDE7) {
    894                result = new Point2D.Double(x1, y0);
    895            }
    896            else if (anchor == ItemLabelAnchor.OUTSIDE8) {
    897                result = new Point2D.Double(x0, y1);
    898            }
    899            else if (anchor == ItemLabelAnchor.OUTSIDE9) {
    900                result = new Point2D.Double(x0, y3);
    901            }
    902            else if (anchor == ItemLabelAnchor.OUTSIDE10) {
    903                result = new Point2D.Double(x0, y5);
    904            }
    905            else if (anchor == ItemLabelAnchor.OUTSIDE11) {
    906                result = new Point2D.Double(x1, y6);
    907            }
    908            else if (anchor == ItemLabelAnchor.OUTSIDE12) {
    909                result = new Point2D.Double(x3, y6);
    910            }
    911
    912            return result;
    913
    914        }
    915
    916        /**
    917         * Returns <code>true</code> if the specified anchor point is inside a bar.
    918         *
    919         * @param anchor  the anchor point.
    920         *
    921         * @return A boolean.
    922         */
    923        private boolean isInternalAnchor(ItemLabelAnchor anchor) {
    924            return anchor == ItemLabelAnchor.CENTER
    925                   || anchor == ItemLabelAnchor.INSIDE1
    926                   || anchor == ItemLabelAnchor.INSIDE2
    927                   || anchor == ItemLabelAnchor.INSIDE3
    928                   || anchor == ItemLabelAnchor.INSIDE4
    929                   || anchor == ItemLabelAnchor.INSIDE5
    930                   || anchor == ItemLabelAnchor.INSIDE6
    931                   || anchor == ItemLabelAnchor.INSIDE7
    932                   || anchor == ItemLabelAnchor.INSIDE8
    933                   || anchor == ItemLabelAnchor.INSIDE9
    934                   || anchor == ItemLabelAnchor.INSIDE10
    935                   || anchor == ItemLabelAnchor.INSIDE11
    936                   || anchor == ItemLabelAnchor.INSIDE12;
    937        }
    938
    939        /**
    940         * Tests this instance for equality with an arbitrary object.
    941         *
    942         * @param obj  the object (<code>null</code> permitted).
    943         *
    944         * @return A boolean.
    945         */
    946        public boolean equals(Object obj) {
    947
    948            if (obj == this) {
    949                return true;
    950            }
    951            if (!(obj instanceof BarRenderer)) {
    952                return false;
    953            }
    954            if (!super.equals(obj)) {
    955                return false;
    956            }
    957            BarRenderer that = (BarRenderer) obj;
    958            if (this.base != that.base) {
    959                return false;
    960            }
    961            if (this.itemMargin != that.itemMargin) {
    962                return false;
    963            }
    964            if (this.drawBarOutline != that.drawBarOutline) {
    965                return false;
    966            }
    967            if (this.maximumBarWidth != that.maximumBarWidth) {
    968                return false;
    969            }
    970            if (this.minimumBarLength != that.minimumBarLength) {
    971                return false;
    972            }
    973            if (!ObjectUtilities.equal(this.gradientPaintTransformer,
    974                    that.gradientPaintTransformer)) {
    975                return false;
    976            }
    977            if (!ObjectUtilities.equal(
    978                this.positiveItemLabelPositionFallback,
    979                that.positiveItemLabelPositionFallback
    980            )) {
    981                return false;
    982            }
    983            if (!ObjectUtilities.equal(
    984                this.negativeItemLabelPositionFallback,
    985                that.negativeItemLabelPositionFallback
    986            )) {
    987                return false;
    988            }
    989
    990            return true;
    991
    992        }
    993
    994    }