    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2007, 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     * StackedBarRenderer3D.java
    029     * -------------------------
    030     * (C) Copyright 2000-2007, by Serge V. Grachov and Contributors.
    031     *
    032     * Original Author:  Serge V. Grachov;
    033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
    034     *                   Richard Atkinson;
    035     *                   Christian W. Zuckschwerdt;
    036     *                   Max Herfort (patch 1459313);
    037     *
    038     * Changes
    039     * -------
    040     * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
    041     * 15-Nov-2001 : Modified to allow for null data values (DG);
    042     * 13-Dec-2001 : Added tooltips (DG);
    043     * 15-Feb-2002 : Added isStacked() method (DG);
    044     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
    045     * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
    046     * 25-Jun-2002 : Removed redundant imports (DG);
    047     * 26-Jun-2002 : Small change to entity (DG);
    048     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
    049     *               for HTML image maps (RA);
    050     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    051     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
    052     *               CategoryToolTipGenerator interface (DG);
    053     * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
    054     * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
    055     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
    056     * 25-Mar-2003 : Implemented Serializable (DG);
    057     * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
    058     *               726260) (DG);
    059     * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
    060     *               --> StackedBarRenderer3D (DG);
    061     * 30-Jul-2003 : Modified entity constructor (CZ);
    062     * 07-Oct-2003 : Added renderer state (DG);
    063     * 21-Nov-2003 : Added a new constructor (DG);
    064     * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
    065     * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
    066     * 05-Nov-2004 : Modified drawItem() signature (DG);
    067     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
    068     * 18-Mar-2005 : Override for getPassCount() method (DG);
    069     * 20-Apr-2005 : Renamed CategoryLabelGenerator
    070     *               --> CategoryItemLabelGenerator (DG);
    071     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
    072     * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
    073     * ------------- JFREECHART 1.0.x ---------------------------------------------
    074     * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
    075     *               by Max Herfort (DG);
    076     * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
    077     * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList()
    078     *               method (DG);
    079     * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
    080     *               see bug report 1599652 (DG);
    081     * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and  1713474
    082     *               (shading) (DG);
    083     *
    084     */
    085
    086    package org.jfree.chart.renderer.category;
    087
    088    import java.awt.Color;
    089    import java.awt.Graphics2D;
    090    import java.awt.Paint;
    091    import java.awt.Shape;
    092    import java.awt.geom.GeneralPath;
    093    import java.awt.geom.Point2D;
    094    import java.awt.geom.Rectangle2D;
    095    import java.io.Serializable;
    096    import java.util.ArrayList;
    097    import java.util.List;
    098
    099    import org.jfree.chart.axis.CategoryAxis;
    100    import org.jfree.chart.axis.ValueAxis;
    101    import org.jfree.chart.entity.EntityCollection;
    102    import org.jfree.chart.event.RendererChangeEvent;
    103    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    104    import org.jfree.chart.plot.CategoryPlot;
    105    import org.jfree.chart.plot.PlotOrientation;
    106    import org.jfree.data.DataUtilities;
    107    import org.jfree.data.Range;
    108    import org.jfree.data.category.CategoryDataset;
    109    import org.jfree.data.general.DatasetUtilities;
    110    import org.jfree.util.BooleanUtilities;
    111    import org.jfree.util.PublicCloneable;
    112
    113    /**
    114     * Renders stacked bars with 3D-effect, for use with the
    115     * {@link org.jfree.chart.plot.CategoryPlot} class.
    116     */
    117    public class StackedBarRenderer3D extends BarRenderer3D
    118                                      implements Cloneable, PublicCloneable,
    119                                                 Serializable {
    120
    121        /** For serialization. */
    122        private static final long serialVersionUID = -5832945916493247123L;
    123
    124        /** A flag that controls whether the bars display values or percentages. */
    125        private boolean renderAsPercentages;
    126
    127        /**
    128         * Creates a new renderer with no tool tip generator and no URL generator.
    129         * <P>
    130         * The defaults (no tool tip or URL generators) have been chosen to
    131         * minimise the processing required to generate a default chart.  If you
    132         * require tool tips or URLs, then you can easily add the required
    133         * generators.
    134         */
    135        public StackedBarRenderer3D() {
    136            this(false);
    137        }
    138
    139        /**
    140         * Constructs a new renderer with the specified '3D effect'.
    141         *
    142         * @param xOffset  the x-offset for the 3D effect.
    143         * @param yOffset  the y-offset for the 3D effect.
    144         */
    145        public StackedBarRenderer3D(double xOffset, double yOffset) {
    146            super(xOffset, yOffset);
    147        }
    148
    149        /**
    150         * Creates a new renderer.
    151         *
    152         * @param renderAsPercentages  a flag that controls whether the data values
    153         *                             are rendered as percentages.
    154         *
    155         * @since 1.0.2
    156         */
    157        public StackedBarRenderer3D(boolean renderAsPercentages) {
    158            super();
    159            this.renderAsPercentages = renderAsPercentages;
    160        }
    161
    162        /**
    163         * Constructs a new renderer with the specified '3D effect'.
    164         *
    165         * @param xOffset  the x-offset for the 3D effect.
    166         * @param yOffset  the y-offset for the 3D effect.
    167         * @param renderAsPercentages  a flag that controls whether the data values
    168         *                             are rendered as percentages.
    169         *
    170         * @since 1.0.2
    171         */
    172        public StackedBarRenderer3D(double xOffset, double yOffset,
    173                boolean renderAsPercentages) {
    174            super(xOffset, yOffset);
    175            this.renderAsPercentages = renderAsPercentages;
    176        }
    177
    178        /**
    179         * Returns <code>true</code> if the renderer displays each item value as
    180         * a percentage (so that the stacked bars add to 100%), and
    181         * <code>false</code> otherwise.
    182         *
    183         * @return A boolean.
    184         *
    185         * @since 1.0.2
    186         */
    187        public boolean getRenderAsPercentages() {
    188            return this.renderAsPercentages;
    189        }
    190
    191        /**
    192         * Sets the flag that controls whether the renderer displays each item
    193         * value as a percentage (so that the stacked bars add to 100%), and sends
    194         * a {@link RendererChangeEvent} to all registered listeners.
    195         *
    196         * @param asPercentages  the flag.
    197         *
    198         * @since 1.0.2
    199         */
    200        public void setRenderAsPercentages(boolean asPercentages) {
    201            this.renderAsPercentages = asPercentages;
    202            fireChangeEvent();
    203        }
    204
    205        /**
    206         * Returns the range of values the renderer requires to display all the
    207         * items from the specified dataset.
    208         *
    209         * @param dataset  the dataset (<code>null</code> not permitted).
    210         *
    211         * @return The range (or <code>null</code> if the dataset is empty).
    212         */
    213        public Range findRangeBounds(CategoryDataset dataset) {
    214            if (this.renderAsPercentages) {
    215                return new Range(0.0, 1.0);
    216            }
    217            else {
    218                return DatasetUtilities.findStackedRangeBounds(dataset);
    219            }
    220        }
    221
    222        /**
    223         * Calculates the bar width and stores it in the renderer state.
    224         *
    225         * @param plot  the plot.
    226         * @param dataArea  the data area.
    227         * @param rendererIndex  the renderer index.
    228         * @param state  the renderer state.
    229         */
    230        protected void calculateBarWidth(CategoryPlot plot,
    231                                         Rectangle2D dataArea,
    232                                         int rendererIndex,
    233                                         CategoryItemRendererState state) {
    234
    235            // calculate the bar width
    236            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    237            CategoryDataset data = plot.getDataset(rendererIndex);
    238            if (data != null) {
    239                PlotOrientation orientation = plot.getOrientation();
    240                double space = 0.0;
    241                if (orientation == PlotOrientation.HORIZONTAL) {
    242                    space = dataArea.getHeight();
    243                }
    244                else if (orientation == PlotOrientation.VERTICAL) {
    245                    space = dataArea.getWidth();
    246                }
    247                double maxWidth = space * getMaximumBarWidth();
    248                int columns = data.getColumnCount();
    249                double categoryMargin = 0.0;
    250                if (columns > 1) {
    251                    categoryMargin = domainAxis.getCategoryMargin();
    252                }
    253
    254                double used = space * (1 - domainAxis.getLowerMargin()
    255                                         - domainAxis.getUpperMargin()
    256                                         - categoryMargin);
    257                if (columns > 0) {
    258                    state.setBarWidth(Math.min(used / columns, maxWidth));
    259                }
    260                else {
    261                    state.setBarWidth(Math.min(used, maxWidth));
    262                }
    263            }
    264
    265        }
    266
    267        /**
    268         * Returns a list containing the stacked values for the specified series
    269         * in the given dataset, plus the supplied base value.
    270         *
    271         * @param dataset  the dataset (<code>null</code> not permitted).
    272         * @param category  the category key (<code>null</code> not permitted).
    273         * @param base  the base value.
    274         * @param asPercentages  a flag that controls whether the values in the
    275         *     list are converted to percentages of the total.
    276         *
    277         * @return The value list.
    278         *
    279         * @since 1.0.4
    280         */
    281        protected static List createStackedValueList(CategoryDataset dataset,
    282                Comparable category, double base, boolean asPercentages) {
    283
    284            List result = new ArrayList();
    285            double posBase = base;
    286            double negBase = base;
    287            double total = 0.0;
    288            if (asPercentages) {
    289                total = DataUtilities.calculateColumnTotal(dataset,
    290                        dataset.getColumnIndex(category));
    291            }
    292
    293            int baseIndex = -1;
    294            int seriesCount = dataset.getRowCount();
    295            for (int s = 0; s < seriesCount; s++) {
    296                Number n = dataset.getValue(dataset.getRowKey(s), category);
    297                if (n == null) {
    298                    continue;
    299                }
    300                double v = n.doubleValue();
    301                if (asPercentages) {
    302                    v = v / total;
    303                }
    304                if (v >= 0.0) {
    305                    if (baseIndex < 0) {
    306                        result.add(new Object[] {null, new Double(base)});
    307                        baseIndex = 0;
    308                    }
    309                    posBase = posBase + v;
    310                    result.add(new Object[] {new Integer(s), new Double(posBase)});
    311                }
    312                else if (v < 0.0) {
    313                    if (baseIndex < 0) {
    314                        result.add(new Object[] {null, new Double(base)});
    315                        baseIndex = 0;
    316                    }
    317                    negBase = negBase + v; // '+' because v is negative
    318                    result.add(0, new Object[] {new Integer(-s),
    319                            new Double(negBase)});
    320                    baseIndex++;
    321                }
    322            }
    323            return result;
    324
    325        }
    326
    327        /**
    328         * Draws the visual representation of one data item from the chart (in
    329         * fact, this method does nothing until it reaches the last item for each
    330         * category, at which point it draws all the items for that category).
    331         *
    332         * @param g2  the graphics device.
    333         * @param state  the renderer state.
    334         * @param dataArea  the plot area.
    335         * @param plot  the plot.
    336         * @param domainAxis  the domain (category) axis.
    337         * @param rangeAxis  the range (value) axis.
    338         * @param dataset  the data.
    339         * @param row  the row index (zero-based).
    340         * @param column  the column index (zero-based).
    341         * @param pass  the pass index.
    342         */
    343        public void drawItem(Graphics2D g2,
    344                             CategoryItemRendererState state,
    345                             Rectangle2D dataArea,
    346                             CategoryPlot plot,
    347                             CategoryAxis domainAxis,
    348                             ValueAxis rangeAxis,
    349                             CategoryDataset dataset,
    350                             int row,
    351                             int column,
    352                             int pass) {
    353
    354            // wait till we are at the last item for the row then draw the
    355            // whole stack at once
    356            if (row < dataset.getRowCount() - 1) {
    357                return;
    358            }
    359            Comparable category = dataset.getColumnKey(column);
    360
    361            List values = createStackedValueList(dataset,
    362                    dataset.getColumnKey(column), getBase(),
    363                    this.renderAsPercentages);
    364
    365            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    366                    dataArea.getY() + getYOffset(),
    367                    dataArea.getWidth() - getXOffset(),
    368                    dataArea.getHeight() - getYOffset());
    369
    370
    371            PlotOrientation orientation = plot.getOrientation();
    372
    373            // handle rendering separately for the two plot orientations...
    374            if (orientation == PlotOrientation.HORIZONTAL) {
    375                drawStackHorizontal(values, category, g2, state, adjusted, plot,
    376                        domainAxis, rangeAxis, dataset);
    377            }
    378            else {
    379                drawStackVertical(values, category, g2, state, adjusted, plot,
    380                        domainAxis, rangeAxis, dataset);
    381            }
    382
    383        }
    384
    385        /**
    386         * Draws a stack of bars for one category, with a horizontal orientation.
    387         *
    388         * @param values  the value list.
    389         * @param category  the category.
    390         * @param g2  the graphics device.
    391         * @param state  the state.
    392         * @param dataArea  the data area (adjusted for the 3D effect).
    393         * @param plot  the plot.
    394         * @param domainAxis  the domain axis.
    395         * @param rangeAxis  the range axis.
    396         * @param dataset  the dataset.
    397         *
    398         * @since 1.0.4
    399         */
    400        protected void drawStackHorizontal(List values, Comparable category,
    401                Graphics2D g2, CategoryItemRendererState state,
    402                Rectangle2D dataArea, CategoryPlot plot,
    403                CategoryAxis domainAxis, ValueAxis rangeAxis,
    404                CategoryDataset dataset) {
    405
    406            int column = dataset.getColumnIndex(category);
    407            double barX0 = domainAxis.getCategoryMiddle(column,
    408                    dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
    409                    - state.getBarWidth() / 2.0;
    410            double barW = state.getBarWidth();
    411
    412            // a list to store the series index and bar region, so we can draw
    413            // all the labels at the end...
    414            List itemLabelList = new ArrayList();
    415
    416            // draw the blocks
    417            boolean inverted = rangeAxis.isInverted();
    418            int blockCount = values.size() - 1;
    419            for (int k = 0; k < blockCount; k++) {
    420                int index = (inverted ? blockCount - k - 1 : k);
    421                Object[] prev = (Object[]) values.get(index);
    422                Object[] curr = (Object[]) values.get(index + 1);
    423                int series = 0;
    424                if (curr[0] == null) {
    425                    series = -((Integer) prev[0]).intValue();
    426                }
    427                else {
    428                    series = ((Integer) curr[0]).intValue();
    429                    if (series < 0) {
    430                        series = -((Integer) prev[0]).intValue();
    431                    }
    432                }
    433                double v0 = ((Double) prev[1]).doubleValue();
    434                double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
    435                        plot.getRangeAxisEdge());
    436
    437                double v1 = ((Double) curr[1]).doubleValue();
    438                double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
    439                        plot.getRangeAxisEdge());
    440
    441                Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
    442                        inverted);
    443                Paint fillPaint = getItemPaint(series, column);
    444                Paint fillPaintDark = fillPaint;
    445                if (fillPaintDark instanceof Color) {
    446                    fillPaintDark = ((Color) fillPaint).darker();
    447                }
    448                boolean drawOutlines = isDrawBarOutline();
    449                Paint outlinePaint = fillPaint;
    450                if (drawOutlines) {
    451                    outlinePaint = getItemOutlinePaint(series, column);
    452                    g2.setStroke(getItemOutlineStroke(series, column));
    453                }
    454                for (int f = 0; f < 6; f++) {
    455                    if (f == 5) {
    456                        g2.setPaint(fillPaint);
    457                    }
    458                    else {
    459                        g2.setPaint(fillPaintDark);
    460                    }
    461                    g2.fill(faces[f]);
    462                    if (drawOutlines) {
    463                        g2.setPaint(outlinePaint);
    464                        g2.draw(faces[f]);
    465                    }
    466                }
    467
    468                itemLabelList.add(new Object[] {new Integer(series),
    469                        faces[5].getBounds2D(),
    470                        BooleanUtilities.valueOf(v0 < getBase())});
    471
    472                // add an item entity, if this information is being collected
    473                EntityCollection entities = state.getEntityCollection();
    474                if (entities != null) {
    475                    addItemEntity(entities, dataset, series, column, faces[5]);
    476                }
    477
    478            }
    479
    480            for (int i = 0; i < itemLabelList.size(); i++) {
    481                Object[] record = (Object[]) itemLabelList.get(i);
    482                int series = ((Integer) record[0]).intValue();
    483                Rectangle2D bar = (Rectangle2D) record[1];
    484                boolean neg = ((Boolean) record[2]).booleanValue();
    485                CategoryItemLabelGenerator generator
    486                        = getItemLabelGenerator(series, column);
    487                if (generator != null && isItemLabelVisible(series, column)) {
    488                    drawItemLabel(g2, dataset, series, column, plot, generator,
    489                            bar, neg);
    490                }
    491
    492            }
    493        }
    494
    495        /**
    496         * Creates an array of shapes representing the six sides of a block in a
    497         * horizontal stack.
    498         *
    499         * @param x0  left edge of bar (in Java2D space).
    500         * @param width  the width of the bar (in Java2D units).
    501         * @param y0  the base of the block (in Java2D space).
    502         * @param y1  the top of the block (in Java2D space).
    503         * @param inverted  a flag indicating whether or not the block is inverted
    504         *     (this changes the order of the faces of the block).
    505         *
    506         * @return The sides of the block.
    507         */
    508        private Shape[] createHorizontalBlock(double x0, double width, double y0,
    509                double y1, boolean inverted) {
    510            Shape[] result = new Shape[6];
    511            Point2D p00 = new Point2D.Double(y0, x0);
    512            Point2D p01 = new Point2D.Double(y0, x0 + width);
    513            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
    514                    p01.getY() - getYOffset());
    515            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
    516                    p00.getY() - getYOffset());
    517
    518            Point2D p0 = new Point2D.Double(y1, x0);
    519            Point2D p1 = new Point2D.Double(y1, x0 + width);
    520            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
    521                    p1.getY() - getYOffset());
    522            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
    523                    p0.getY() - getYOffset());
    524
    525            GeneralPath bottom = new GeneralPath();
    526            bottom.moveTo((float) p1.getX(), (float) p1.getY());
    527            bottom.lineTo((float) p01.getX(), (float) p01.getY());
    528            bottom.lineTo((float) p02.getX(), (float) p02.getY());
    529            bottom.lineTo((float) p2.getX(), (float) p2.getY());
    530            bottom.closePath();
    531
    532            GeneralPath top = new GeneralPath();
    533            top.moveTo((float) p0.getX(), (float) p0.getY());
    534            top.lineTo((float) p00.getX(), (float) p00.getY());
    535            top.lineTo((float) p03.getX(), (float) p03.getY());
    536            top.lineTo((float) p3.getX(), (float) p3.getY());
    537            top.closePath();
    538
    539            GeneralPath back = new GeneralPath();
    540            back.moveTo((float) p2.getX(), (float) p2.getY());
    541            back.lineTo((float) p02.getX(), (float) p02.getY());
    542            back.lineTo((float) p03.getX(), (float) p03.getY());
    543            back.lineTo((float) p3.getX(), (float) p3.getY());
    544            back.closePath();
    545
    546            GeneralPath front = new GeneralPath();
    547            front.moveTo((float) p0.getX(), (float) p0.getY());
    548            front.lineTo((float) p1.getX(), (float) p1.getY());
    549            front.lineTo((float) p01.getX(), (float) p01.getY());
    550            front.lineTo((float) p00.getX(), (float) p00.getY());
    551            front.closePath();
    552
    553            GeneralPath left = new GeneralPath();
    554            left.moveTo((float) p0.getX(), (float) p0.getY());
    555            left.lineTo((float) p1.getX(), (float) p1.getY());
    556            left.lineTo((float) p2.getX(), (float) p2.getY());
    557            left.lineTo((float) p3.getX(), (float) p3.getY());
    558            left.closePath();
    559
    560            GeneralPath right = new GeneralPath();
    561            right.moveTo((float) p00.getX(), (float) p00.getY());
    562            right.lineTo((float) p01.getX(), (float) p01.getY());
    563            right.lineTo((float) p02.getX(), (float) p02.getY());
    564            right.lineTo((float) p03.getX(), (float) p03.getY());
    565            right.closePath();
    566            result[0] = bottom;
    567            result[1] = back;
    568            if (inverted) {
    569                result[2] = right;
    570                result[3] = left;
    571            }
    572            else {
    573                result[2] = left;
    574                result[3] = right;
    575            }
    576            result[4] = top;
    577            result[5] = front;
    578            return result;
    579        }
    580
    581        /**
    582         * Draws a stack of bars for one category, with a vertical orientation.
    583         *
    584         * @param values  the value list.
    585         * @param category  the category.
    586         * @param g2  the graphics device.
    587         * @param state  the state.
    588         * @param dataArea  the data area (adjusted for the 3D effect).
    589         * @param plot  the plot.
    590         * @param domainAxis  the domain axis.
    591         * @param rangeAxis  the range axis.
    592         * @param dataset  the dataset.
    593         *
    594         * @since 1.0.4
    595         */
    596        protected void drawStackVertical(List values, Comparable category,
    597                Graphics2D g2, CategoryItemRendererState state,
    598                Rectangle2D dataArea, CategoryPlot plot,
    599                CategoryAxis domainAxis, ValueAxis rangeAxis,
    600                CategoryDataset dataset) {
    601
    602            int column = dataset.getColumnIndex(category);
    603            double barX0 = domainAxis.getCategoryMiddle(column,
    604                    dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
    605                    - state.getBarWidth() / 2.0;
    606            double barW = state.getBarWidth();
    607
    608            // a list to store the series index and bar region, so we can draw
    609            // all the labels at the end...
    610            List itemLabelList = new ArrayList();
    611
    612            // draw the blocks
    613            boolean inverted = rangeAxis.isInverted();
    614            int blockCount = values.size() - 1;
    615            for (int k = 0; k < blockCount; k++) {
    616                int index = (inverted ? blockCount - k - 1 : k);
    617                Object[] prev = (Object[]) values.get(index);
    618                Object[] curr = (Object[]) values.get(index + 1);
    619                int series = 0;
    620                if (curr[0] == null) {
    621                    series = -((Integer) prev[0]).intValue();
    622                }
    623                else {
    624                    series = ((Integer) curr[0]).intValue();
    625                    if (series < 0) {
    626                        series = -((Integer) prev[0]).intValue();
    627                    }
    628                }
    629                double v0 = ((Double) prev[1]).doubleValue();
    630                double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
    631                        plot.getRangeAxisEdge());
    632
    633                double v1 = ((Double) curr[1]).doubleValue();
    634                double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
    635                        plot.getRangeAxisEdge());
    636
    637                Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
    638                        inverted);
    639                Paint fillPaint = getItemPaint(series, column);
    640                Paint fillPaintDark = fillPaint;
    641                if (fillPaintDark instanceof Color) {
    642                    fillPaintDark = ((Color) fillPaint).darker();
    643                }
    644                boolean drawOutlines = isDrawBarOutline();
    645                Paint outlinePaint = fillPaint;
    646                if (drawOutlines) {
    647                    outlinePaint = getItemOutlinePaint(series, column);
    648                    g2.setStroke(getItemOutlineStroke(series, column));
    649                }
    650
    651                for (int f = 0; f < 6; f++) {
    652                    if (f == 5) {
    653                        g2.setPaint(fillPaint);
    654                    }
    655                    else {
    656                        g2.setPaint(fillPaintDark);
    657                    }
    658                    g2.fill(faces[f]);
    659                    if (drawOutlines) {
    660                        g2.setPaint(outlinePaint);
    661                        g2.draw(faces[f]);
    662                    }
    663                }
    664
    665                itemLabelList.add(new Object[] {new Integer(series),
    666                        faces[5].getBounds2D(),
    667                        BooleanUtilities.valueOf(v0 < getBase())});
    668
    669                // add an item entity, if this information is being collected
    670                EntityCollection entities = state.getEntityCollection();
    671                if (entities != null) {
    672                    addItemEntity(entities, dataset, series, column, faces[5]);
    673                }
    674
    675            }
    676
    677            for (int i = 0; i < itemLabelList.size(); i++) {
    678                Object[] record = (Object[]) itemLabelList.get(i);
    679                int series = ((Integer) record[0]).intValue();
    680                Rectangle2D bar = (Rectangle2D) record[1];
    681                boolean neg = ((Boolean) record[2]).booleanValue();
    682                CategoryItemLabelGenerator generator
    683                        = getItemLabelGenerator(series, column);
    684                if (generator != null && isItemLabelVisible(series, column)) {
    685                    drawItemLabel(g2, dataset, series, column, plot, generator,
    686                            bar, neg);
    687                }
    688
    689            }
    690        }
    691
    692        /**
    693         * Creates an array of shapes representing the six sides of a block in a
    694         * vertical stack.
    695         *
    696         * @param x0  left edge of bar (in Java2D space).
    697         * @param width  the width of the bar (in Java2D units).
    698         * @param y0  the base of the block (in Java2D space).
    699         * @param y1  the top of the block (in Java2D space).
    700         * @param inverted  a flag indicating whether or not the block is inverted
    701         *     (this changes the order of the faces of the block).
    702         *
    703         * @return The sides of the block.
    704         */
    705        private Shape[] createVerticalBlock(double x0, double width, double y0,
    706                double y1, boolean inverted) {
    707            Shape[] result = new Shape[6];
    708            Point2D p00 = new Point2D.Double(x0, y0);
    709            Point2D p01 = new Point2D.Double(x0 + width, y0);
    710            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
    711                    p01.getY() - getYOffset());
    712            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
    713                    p00.getY() - getYOffset());
    714
    715
    716            Point2D p0 = new Point2D.Double(x0, y1);
    717            Point2D p1 = new Point2D.Double(x0 + width, y1);
    718            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
    719                    p1.getY() - getYOffset());
    720            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
    721                    p0.getY() - getYOffset());
    722
    723            GeneralPath right = new GeneralPath();
    724            right.moveTo((float) p1.getX(), (float) p1.getY());
    725            right.lineTo((float) p01.getX(), (float) p01.getY());
    726            right.lineTo((float) p02.getX(), (float) p02.getY());
    727            right.lineTo((float) p2.getX(), (float) p2.getY());
    728            right.closePath();
    729
    730            GeneralPath left = new GeneralPath();
    731            left.moveTo((float) p0.getX(), (float) p0.getY());
    732            left.lineTo((float) p00.getX(), (float) p00.getY());
    733            left.lineTo((float) p03.getX(), (float) p03.getY());
    734            left.lineTo((float) p3.getX(), (float) p3.getY());
    735            left.closePath();
    736
    737            GeneralPath back = new GeneralPath();
    738            back.moveTo((float) p2.getX(), (float) p2.getY());
    739            back.lineTo((float) p02.getX(), (float) p02.getY());
    740            back.lineTo((float) p03.getX(), (float) p03.getY());
    741            back.lineTo((float) p3.getX(), (float) p3.getY());
    742            back.closePath();
    743
    744            GeneralPath front = new GeneralPath();
    745            front.moveTo((float) p0.getX(), (float) p0.getY());
    746            front.lineTo((float) p1.getX(), (float) p1.getY());
    747            front.lineTo((float) p01.getX(), (float) p01.getY());
    748            front.lineTo((float) p00.getX(), (float) p00.getY());
    749            front.closePath();
    750
    751            GeneralPath top = new GeneralPath();
    752            top.moveTo((float) p0.getX(), (float) p0.getY());
    753            top.lineTo((float) p1.getX(), (float) p1.getY());
    754            top.lineTo((float) p2.getX(), (float) p2.getY());
    755            top.lineTo((float) p3.getX(), (float) p3.getY());
    756            top.closePath();
    757
    758            GeneralPath bottom = new GeneralPath();
    759            bottom.moveTo((float) p00.getX(), (float) p00.getY());
    760            bottom.lineTo((float) p01.getX(), (float) p01.getY());
    761            bottom.lineTo((float) p02.getX(), (float) p02.getY());
    762            bottom.lineTo((float) p03.getX(), (float) p03.getY());
    763            bottom.closePath();
    764
    765            result[0] = bottom;
    766            result[1] = back;
    767            result[2] = left;
    768            result[3] = right;
    769            result[4] = top;
    770            result[5] = front;
    771            if (inverted) {
    772                result[0] = top;
    773                result[4] = bottom;
    774            }
    775            return result;
    776        }
    777
    778        /**
    779         * Tests this renderer for equality with an arbitrary object.
    780         *
    781         * @param obj  the object (<code>null</code> permitted).
    782         *
    783         * @return A boolean.
    784         */
    785        public boolean equals(Object obj) {
    786            if (obj == this) {
    787                return true;
    788            }
    789            if (!(obj instanceof StackedBarRenderer3D)) {
    790                return false;
    791            }
    792            if (!super.equals(obj)) {
    793                return false;
    794            }
    795            StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
    796            if (this.renderAsPercentages != that.getRenderAsPercentages()) {
    797                return false;
    798            }
    799            return true;
    800        }
    801
    802    }