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