    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2011, 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     * [Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    025     * Other names may be trademarks of their respective owners.]
    026     *
    027     * -------------------------
    028     * StackedBarRenderer3D.java
    029     * -------------------------
    030     * (C) Copyright 2000-2009, 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     * 15-Aug-2008 : Fixed bug 2031407 - no negative zero for stack encoding (DG);
    084     * 03-Feb-2009 : Fixed regression in findRangeBounds() method for null
    085     *               dataset (DG);
    086     * 04-Feb-2009 : Handle seriesVisible flag (DG);
    087     * 07-Jul-2009 : Added flag for handling zero values (DG);
    088     *
    089     */
    090
    091    package org.jfree.chart.renderer.category;
    092
    093    import java.awt.Color;
    094    import java.awt.Graphics2D;
    095    import java.awt.Paint;
    096    import java.awt.Shape;
    097    import java.awt.geom.GeneralPath;
    098    import java.awt.geom.Point2D;
    099    import java.awt.geom.Rectangle2D;
    100    import java.io.Serializable;
    101    import java.util.ArrayList;
    102    import java.util.List;
    103
    104    import org.jfree.chart.HashUtilities;
    105    import org.jfree.chart.axis.CategoryAxis;
    106    import org.jfree.chart.axis.ValueAxis;
    107    import org.jfree.chart.entity.EntityCollection;
    108    import org.jfree.chart.event.RendererChangeEvent;
    109    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    110    import org.jfree.chart.plot.CategoryPlot;
    111    import org.jfree.chart.plot.PlotOrientation;
    112    import org.jfree.data.DataUtilities;
    113    import org.jfree.data.Range;
    114    import org.jfree.data.category.CategoryDataset;
    115    import org.jfree.data.general.DatasetUtilities;
    116    import org.jfree.util.BooleanUtilities;
    117    import org.jfree.util.PublicCloneable;
    118
    119    /**
    120     * Renders stacked bars with 3D-effect, for use with the {@link CategoryPlot}
    121     * class.  The example shown here is generated by the
    122     * <code>StackedBarChart3DDemo1.java</code> program included in the
    123     * JFreeChart Demo Collection:
    124     * <br><br>
    125     * <img src="../../../../../images/StackedBarRenderer3DSample.png"
    126     * alt="StackedBarRenderer3DSample.png" />
    127     */
    128    public class StackedBarRenderer3D extends BarRenderer3D
    129            implements Cloneable, PublicCloneable, Serializable {
    130
    131        /** For serialization. */
    132        private static final long serialVersionUID = -5832945916493247123L;
    133
    134        /** A flag that controls whether the bars display values or percentages. */
    135        private boolean renderAsPercentages;
    136
    137        /**
    138         * A flag that controls whether or not zero values are drawn by the
    139         * renderer.
    140         *
    141         * @since 1.0.14
    142         */
    143        private boolean ignoreZeroValues;
    144
    145        /**
    146         * Creates a new renderer with no tool tip generator and no URL generator.
    147         * <P>
    148         * The defaults (no tool tip or URL generators) have been chosen to
    149         * minimise the processing required to generate a default chart.  If you
    150         * require tool tips or URLs, then you can easily add the required
    151         * generators.
    152         */
    153        public StackedBarRenderer3D() {
    154            this(false);
    155        }
    156
    157        /**
    158         * Constructs a new renderer with the specified '3D effect'.
    159         *
    160         * @param xOffset  the x-offset for the 3D effect.
    161         * @param yOffset  the y-offset for the 3D effect.
    162         */
    163        public StackedBarRenderer3D(double xOffset, double yOffset) {
    164            super(xOffset, yOffset);
    165        }
    166
    167        /**
    168         * Creates a new renderer.
    169         *
    170         * @param renderAsPercentages  a flag that controls whether the data values
    171         *                             are rendered as percentages.
    172         *
    173         * @since 1.0.2
    174         */
    175        public StackedBarRenderer3D(boolean renderAsPercentages) {
    176            super();
    177            this.renderAsPercentages = renderAsPercentages;
    178        }
    179
    180        /**
    181         * Constructs a new renderer with the specified '3D effect'.
    182         *
    183         * @param xOffset  the x-offset for the 3D effect.
    184         * @param yOffset  the y-offset for the 3D effect.
    185         * @param renderAsPercentages  a flag that controls whether the data values
    186         *                             are rendered as percentages.
    187         *
    188         * @since 1.0.2
    189         */
    190        public StackedBarRenderer3D(double xOffset, double yOffset,
    191                boolean renderAsPercentages) {
    192            super(xOffset, yOffset);
    193            this.renderAsPercentages = renderAsPercentages;
    194        }
    195
    196        /**
    197         * Returns <code>true</code> if the renderer displays each item value as
    198         * a percentage (so that the stacked bars add to 100%), and
    199         * <code>false</code> otherwise.
    200         *
    201         * @return A boolean.
    202         *
    203         * @since 1.0.2
    204         */
    205        public boolean getRenderAsPercentages() {
    206            return this.renderAsPercentages;
    207        }
    208
    209        /**
    210         * Sets the flag that controls whether the renderer displays each item
    211         * value as a percentage (so that the stacked bars add to 100%), and sends
    212         * a {@link RendererChangeEvent} to all registered listeners.
    213         *
    214         * @param asPercentages  the flag.
    215         *
    216         * @since 1.0.2
    217         */
    218        public void setRenderAsPercentages(boolean asPercentages) {
    219            this.renderAsPercentages = asPercentages;
    220            fireChangeEvent();
    221        }
    222
    223        /**
    224         * Returns the flag that controls whether or not zero values are drawn
    225         * by the renderer.
    226         *
    227         * @return A boolean.
    228         *
    229         * @since 1.0.14
    230         */
    231        public boolean getIgnoreZeroValues() {
    232            return this.ignoreZeroValues;
    233        }
    234
    235        /**
    236         * Sets a flag that controls whether or not zero values are drawn by the
    237         * renderer, and sends a {@link RendererChangeEvent} to all registered
    238         * listeners.
    239         *
    240         * @param ignore  the new flag value.
    241         *
    242         * @since 1.0.14
    243         */
    244        public void setIgnoreZeroValues(boolean ignore) {
    245            this.ignoreZeroValues = ignore;
    246            notifyListeners(new RendererChangeEvent(this));
    247        }
    248
    249        /**
    250         * Returns the range of values the renderer requires to display all the
    251         * items from the specified dataset.
    252         *
    253         * @param dataset  the dataset (<code>null</code> not permitted).
    254         *
    255         * @return The range (or <code>null</code> if the dataset is empty).
    256         */
    257        public Range findRangeBounds(CategoryDataset dataset) {
    258            if (dataset == null) {
    259                return null;
    260            }
    261            if (this.renderAsPercentages) {
    262                return new Range(0.0, 1.0);
    263            }
    264            else {
    265                return DatasetUtilities.findStackedRangeBounds(dataset);
    266            }
    267        }
    268
    269        /**
    270         * Calculates the bar width and stores it in the renderer state.
    271         *
    272         * @param plot  the plot.
    273         * @param dataArea  the data area.
    274         * @param rendererIndex  the renderer index.
    275         * @param state  the renderer state.
    276         */
    277        protected void calculateBarWidth(CategoryPlot plot,
    278                                         Rectangle2D dataArea,
    279                                         int rendererIndex,
    280                                         CategoryItemRendererState state) {
    281
    282            // calculate the bar width
    283            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    284            CategoryDataset data = plot.getDataset(rendererIndex);
    285            if (data != null) {
    286                PlotOrientation orientation = plot.getOrientation();
    287                double space = 0.0;
    288                if (orientation == PlotOrientation.HORIZONTAL) {
    289                    space = dataArea.getHeight();
    290                }
    291                else if (orientation == PlotOrientation.VERTICAL) {
    292                    space = dataArea.getWidth();
    293                }
    294                double maxWidth = space * getMaximumBarWidth();
    295                int columns = data.getColumnCount();
    296                double categoryMargin = 0.0;
    297                if (columns > 1) {
    298                    categoryMargin = domainAxis.getCategoryMargin();
    299                }
    300
    301                double used = space * (1 - domainAxis.getLowerMargin()
    302                                         - domainAxis.getUpperMargin()
    303                                         - categoryMargin);
    304                if (columns > 0) {
    305                    state.setBarWidth(Math.min(used / columns, maxWidth));
    306                }
    307                else {
    308                    state.setBarWidth(Math.min(used, maxWidth));
    309                }
    310            }
    311
    312        }
    313
    314        /**
    315         * Returns a list containing the stacked values for the specified series
    316         * in the given dataset, plus the supplied base value.
    317         *
    318         * @param dataset  the dataset (<code>null</code> not permitted).
    319         * @param category  the category key (<code>null</code> not permitted).
    320         * @param base  the base value.
    321         * @param asPercentages  a flag that controls whether the values in the
    322         *     list are converted to percentages of the total.
    323         *
    324         * @return The value list.
    325         *
    326         * @since 1.0.4
    327         *
    328         * @deprecated As of 1.0.13, use {@link #createStackedValueList(
    329         *     CategoryDataset, Comparable, int[], double, boolean)}.
    330         */
    331        protected List createStackedValueList(CategoryDataset dataset,
    332                Comparable category, double base, boolean asPercentages) {
    333            int[] rows = new int[dataset.getRowCount()];
    334            for (int i = 0; i < rows.length; i++) {
    335                rows[i] = i;
    336            }
    337            return createStackedValueList(dataset, category, rows, base,
    338                    asPercentages);
    339        }
    340
    341        /**
    342         * Returns a list containing the stacked values for the specified series
    343         * in the given dataset, plus the supplied base value.
    344         *
    345         * @param dataset  the dataset (<code>null</code> not permitted).
    346         * @param category  the category key (<code>null</code> not permitted).
    347         * @param includedRows  the included rows.
    348         * @param base  the base value.
    349         * @param asPercentages  a flag that controls whether the values in the
    350         *     list are converted to percentages of the total.
    351         *
    352         * @return The value list.
    353         *
    354         * @since 1.0.13
    355         */
    356        protected List createStackedValueList(CategoryDataset dataset,
    357                Comparable category, int[] includedRows, double base,
    358                boolean asPercentages) {
    359
    360            List result = new ArrayList();
    361            double posBase = base;
    362            double negBase = base;
    363            double total = 0.0;
    364            if (asPercentages) {
    365                total = DataUtilities.calculateColumnTotal(dataset,
    366                        dataset.getColumnIndex(category), includedRows);
    367            }
    368
    369            int baseIndex = -1;
    370            int rowCount = includedRows.length;
    371            for (int i = 0; i < rowCount; i++) {
    372                int r = includedRows[i];
    373                Number n = dataset.getValue(dataset.getRowKey(r), category);
    374                if (n == null) {
    375                    continue;
    376                }
    377                double v = n.doubleValue();
    378                if (asPercentages) {
    379                    v = v / total;
    380                }
    381                if ((v > 0.0) || (!this.ignoreZeroValues && v >= 0.0)) {
    382                    if (baseIndex < 0) {
    383                        result.add(new Object[] {null, new Double(base)});
    384                        baseIndex = 0;
    385                    }
    386                    posBase = posBase + v;
    387                    result.add(new Object[] {new Integer(r), new Double(posBase)});
    388                }
    389                else if (v < 0.0) {
    390                    if (baseIndex < 0) {
    391                        result.add(new Object[] {null, new Double(base)});
    392                        baseIndex = 0;
    393                    }
    394                    negBase = negBase + v; // '+' because v is negative
    395                    result.add(0, new Object[] {new Integer(-r - 1),
    396                            new Double(negBase)});
    397                    baseIndex++;
    398                }
    399            }
    400            return result;
    401
    402        }
    403
    404        /**
    405         * Draws the visual representation of one data item from the chart (in
    406         * fact, this method does nothing until it reaches the last item for each
    407         * category, at which point it draws all the items for that category).
    408         *
    409         * @param g2  the graphics device.
    410         * @param state  the renderer state.
    411         * @param dataArea  the plot area.
    412         * @param plot  the plot.
    413         * @param domainAxis  the domain (category) axis.
    414         * @param rangeAxis  the range (value) axis.
    415         * @param dataset  the data.
    416         * @param row  the row index (zero-based).
    417         * @param column  the column index (zero-based).
    418         * @param pass  the pass index.
    419         */
    420        public void drawItem(Graphics2D g2, CategoryItemRendererState state,
    421                Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
    422                ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
    423                int pass) {
    424
    425            // wait till we are at the last item for the row then draw the
    426            // whole stack at once
    427            if (row < dataset.getRowCount() - 1) {
    428                return;
    429            }
    430            Comparable category = dataset.getColumnKey(column);
    431
    432            List values = createStackedValueList(dataset,
    433                    dataset.getColumnKey(column), state.getVisibleSeriesArray(),
    434                    getBase(), this.renderAsPercentages);
    435
    436            Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
    437                    dataArea.getY() + getYOffset(),
    438                    dataArea.getWidth() - getXOffset(),
    439                    dataArea.getHeight() - getYOffset());
    440
    441
    442            PlotOrientation orientation = plot.getOrientation();
    443
    444            // handle rendering separately for the two plot orientations...
    445            if (orientation == PlotOrientation.HORIZONTAL) {
    446                drawStackHorizontal(values, category, g2, state, adjusted, plot,
    447                        domainAxis, rangeAxis, dataset);
    448            }
    449            else {
    450                drawStackVertical(values, category, g2, state, adjusted, plot,
    451                        domainAxis, rangeAxis, dataset);
    452            }
    453
    454        }
    455
    456        /**
    457         * Draws a stack of bars for one category, with a horizontal orientation.
    458         *
    459         * @param values  the value list.
    460         * @param category  the category.
    461         * @param g2  the graphics device.
    462         * @param state  the state.
    463         * @param dataArea  the data area (adjusted for the 3D effect).
    464         * @param plot  the plot.
    465         * @param domainAxis  the domain axis.
    466         * @param rangeAxis  the range axis.
    467         * @param dataset  the dataset.
    468         *
    469         * @since 1.0.4
    470         */
    471        protected void drawStackHorizontal(List values, Comparable category,
    472                Graphics2D g2, CategoryItemRendererState state,
    473                Rectangle2D dataArea, CategoryPlot plot,
    474                CategoryAxis domainAxis, ValueAxis rangeAxis,
    475                CategoryDataset dataset) {
    476
    477            int column = dataset.getColumnIndex(category);
    478            double barX0 = domainAxis.getCategoryMiddle(column,
    479                    dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
    480                    - state.getBarWidth() / 2.0;
    481            double barW = state.getBarWidth();
    482
    483            // a list to store the series index and bar region, so we can draw
    484            // all the labels at the end...
    485            List itemLabelList = new ArrayList();
    486
    487            // draw the blocks
    488            boolean inverted = rangeAxis.isInverted();
    489            int blockCount = values.size() - 1;
    490            for (int k = 0; k < blockCount; k++) {
    491                int index = (inverted ? blockCount - k - 1 : k);
    492                Object[] prev = (Object[]) values.get(index);
    493                Object[] curr = (Object[]) values.get(index + 1);
    494                int series = 0;
    495                if (curr[0] == null) {
    496                    series = -((Integer) prev[0]).intValue() - 1;
    497                }
    498                else {
    499                    series = ((Integer) curr[0]).intValue();
    500                    if (series < 0) {
    501                        series = -((Integer) prev[0]).intValue() - 1;
    502                    }
    503                }
    504                double v0 = ((Double) prev[1]).doubleValue();
    505                double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
    506                        plot.getRangeAxisEdge());
    507
    508                double v1 = ((Double) curr[1]).doubleValue();
    509                double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
    510                        plot.getRangeAxisEdge());
    511
    512                Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
    513                        inverted);
    514                Paint fillPaint = getItemPaint(series, column);
    515                Paint fillPaintDark = fillPaint;
    516                if (fillPaintDark instanceof Color) {
    517                    fillPaintDark = ((Color) fillPaint).darker();
    518                }
    519                boolean drawOutlines = isDrawBarOutline();
    520                Paint outlinePaint = fillPaint;
    521                if (drawOutlines) {
    522                    outlinePaint = getItemOutlinePaint(series, column);
    523                    g2.setStroke(getItemOutlineStroke(series, column));
    524                }
    525                for (int f = 0; f < 6; f++) {
    526                    if (f == 5) {
    527                        g2.setPaint(fillPaint);
    528                    }
    529                    else {
    530                        g2.setPaint(fillPaintDark);
    531                    }
    532                    g2.fill(faces[f]);
    533                    if (drawOutlines) {
    534                        g2.setPaint(outlinePaint);
    535                        g2.draw(faces[f]);
    536                    }
    537                }
    538
    539                itemLabelList.add(new Object[] {new Integer(series),
    540                        faces[5].getBounds2D(),
    541                        BooleanUtilities.valueOf(v0 < getBase())});
    542
    543                // add an item entity, if this information is being collected
    544                EntityCollection entities = state.getEntityCollection();
    545                if (entities != null) {
    546                    addItemEntity(entities, dataset, series, column, faces[5]);
    547                }
    548
    549            }
    550
    551            for (int i = 0; i < itemLabelList.size(); i++) {
    552                Object[] record = (Object[]) itemLabelList.get(i);
    553                int series = ((Integer) record[0]).intValue();
    554                Rectangle2D bar = (Rectangle2D) record[1];
    555                boolean neg = ((Boolean) record[2]).booleanValue();
    556                CategoryItemLabelGenerator generator
    557                        = getItemLabelGenerator(series, column);
    558                if (generator != null && isItemLabelVisible(series, column)) {
    559                    drawItemLabel(g2, dataset, series, column, plot, generator,
    560                            bar, neg);
    561                }
    562
    563            }
    564        }
    565
    566        /**
    567         * Creates an array of shapes representing the six sides of a block in a
    568         * horizontal stack.
    569         *
    570         * @param x0  left edge of bar (in Java2D space).
    571         * @param width  the width of the bar (in Java2D units).
    572         * @param y0  the base of the block (in Java2D space).
    573         * @param y1  the top of the block (in Java2D space).
    574         * @param inverted  a flag indicating whether or not the block is inverted
    575         *     (this changes the order of the faces of the block).
    576         *
    577         * @return The sides of the block.
    578         */
    579        private Shape[] createHorizontalBlock(double x0, double width, double y0,
    580                double y1, boolean inverted) {
    581            Shape[] result = new Shape[6];
    582            Point2D p00 = new Point2D.Double(y0, x0);
    583            Point2D p01 = new Point2D.Double(y0, x0 + width);
    584            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
    585                    p01.getY() - getYOffset());
    586            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
    587                    p00.getY() - getYOffset());
    588
    589            Point2D p0 = new Point2D.Double(y1, x0);
    590            Point2D p1 = new Point2D.Double(y1, x0 + width);
    591            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
    592                    p1.getY() - getYOffset());
    593            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
    594                    p0.getY() - getYOffset());
    595
    596            GeneralPath bottom = new GeneralPath();
    597            bottom.moveTo((float) p1.getX(), (float) p1.getY());
    598            bottom.lineTo((float) p01.getX(), (float) p01.getY());
    599            bottom.lineTo((float) p02.getX(), (float) p02.getY());
    600            bottom.lineTo((float) p2.getX(), (float) p2.getY());
    601            bottom.closePath();
    602
    603            GeneralPath top = new GeneralPath();
    604            top.moveTo((float) p0.getX(), (float) p0.getY());
    605            top.lineTo((float) p00.getX(), (float) p00.getY());
    606            top.lineTo((float) p03.getX(), (float) p03.getY());
    607            top.lineTo((float) p3.getX(), (float) p3.getY());
    608            top.closePath();
    609
    610            GeneralPath back = new GeneralPath();
    611            back.moveTo((float) p2.getX(), (float) p2.getY());
    612            back.lineTo((float) p02.getX(), (float) p02.getY());
    613            back.lineTo((float) p03.getX(), (float) p03.getY());
    614            back.lineTo((float) p3.getX(), (float) p3.getY());
    615            back.closePath();
    616
    617            GeneralPath front = new GeneralPath();
    618            front.moveTo((float) p0.getX(), (float) p0.getY());
    619            front.lineTo((float) p1.getX(), (float) p1.getY());
    620            front.lineTo((float) p01.getX(), (float) p01.getY());
    621            front.lineTo((float) p00.getX(), (float) p00.getY());
    622            front.closePath();
    623
    624            GeneralPath left = new GeneralPath();
    625            left.moveTo((float) p0.getX(), (float) p0.getY());
    626            left.lineTo((float) p1.getX(), (float) p1.getY());
    627            left.lineTo((float) p2.getX(), (float) p2.getY());
    628            left.lineTo((float) p3.getX(), (float) p3.getY());
    629            left.closePath();
    630
    631            GeneralPath right = new GeneralPath();
    632            right.moveTo((float) p00.getX(), (float) p00.getY());
    633            right.lineTo((float) p01.getX(), (float) p01.getY());
    634            right.lineTo((float) p02.getX(), (float) p02.getY());
    635            right.lineTo((float) p03.getX(), (float) p03.getY());
    636            right.closePath();
    637            result[0] = bottom;
    638            result[1] = back;
    639            if (inverted) {
    640                result[2] = right;
    641                result[3] = left;
    642            }
    643            else {
    644                result[2] = left;
    645                result[3] = right;
    646            }
    647            result[4] = top;
    648            result[5] = front;
    649            return result;
    650        }
    651
    652        /**
    653         * Draws a stack of bars for one category, with a vertical orientation.
    654         *
    655         * @param values  the value list.
    656         * @param category  the category.
    657         * @param g2  the graphics device.
    658         * @param state  the state.
    659         * @param dataArea  the data area (adjusted for the 3D effect).
    660         * @param plot  the plot.
    661         * @param domainAxis  the domain axis.
    662         * @param rangeAxis  the range axis.
    663         * @param dataset  the dataset.
    664         *
    665         * @since 1.0.4
    666         */
    667        protected void drawStackVertical(List values, Comparable category,
    668                Graphics2D g2, CategoryItemRendererState state,
    669                Rectangle2D dataArea, CategoryPlot plot,
    670                CategoryAxis domainAxis, ValueAxis rangeAxis,
    671                CategoryDataset dataset) {
    672
    673            int column = dataset.getColumnIndex(category);
    674            double barX0 = domainAxis.getCategoryMiddle(column,
    675                    dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
    676                    - state.getBarWidth() / 2.0;
    677            double barW = state.getBarWidth();
    678
    679            // a list to store the series index and bar region, so we can draw
    680            // all the labels at the end...
    681            List itemLabelList = new ArrayList();
    682
    683            // draw the blocks
    684            boolean inverted = rangeAxis.isInverted();
    685            int blockCount = values.size() - 1;
    686            for (int k = 0; k < blockCount; k++) {
    687                int index = (inverted ? blockCount - k - 1 : k);
    688                Object[] prev = (Object[]) values.get(index);
    689                Object[] curr = (Object[]) values.get(index + 1);
    690                int series = 0;
    691                if (curr[0] == null) {
    692                    series = -((Integer) prev[0]).intValue() - 1;
    693                }
    694                else {
    695                    series = ((Integer) curr[0]).intValue();
    696                    if (series < 0) {
    697                        series = -((Integer) prev[0]).intValue() - 1;
    698                    }
    699                }
    700                double v0 = ((Double) prev[1]).doubleValue();
    701                double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
    702                        plot.getRangeAxisEdge());
    703
    704                double v1 = ((Double) curr[1]).doubleValue();
    705                double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
    706                        plot.getRangeAxisEdge());
    707
    708                Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
    709                        inverted);
    710                Paint fillPaint = getItemPaint(series, column);
    711                Paint fillPaintDark = fillPaint;
    712                if (fillPaintDark instanceof Color) {
    713                    fillPaintDark = ((Color) fillPaint).darker();
    714                }
    715                boolean drawOutlines = isDrawBarOutline();
    716                Paint outlinePaint = fillPaint;
    717                if (drawOutlines) {
    718                    outlinePaint = getItemOutlinePaint(series, column);
    719                    g2.setStroke(getItemOutlineStroke(series, column));
    720                }
    721
    722                for (int f = 0; f < 6; f++) {
    723                    if (f == 5) {
    724                        g2.setPaint(fillPaint);
    725                    }
    726                    else {
    727                        g2.setPaint(fillPaintDark);
    728                    }
    729                    g2.fill(faces[f]);
    730                    if (drawOutlines) {
    731                        g2.setPaint(outlinePaint);
    732                        g2.draw(faces[f]);
    733                    }
    734                }
    735
    736                itemLabelList.add(new Object[] {new Integer(series),
    737                        faces[5].getBounds2D(),
    738                        BooleanUtilities.valueOf(v0 < getBase())});
    739
    740                // add an item entity, if this information is being collected
    741                EntityCollection entities = state.getEntityCollection();
    742                if (entities != null) {
    743                    addItemEntity(entities, dataset, series, column, faces[5]);
    744                }
    745
    746            }
    747
    748            for (int i = 0; i < itemLabelList.size(); i++) {
    749                Object[] record = (Object[]) itemLabelList.get(i);
    750                int series = ((Integer) record[0]).intValue();
    751                Rectangle2D bar = (Rectangle2D) record[1];
    752                boolean neg = ((Boolean) record[2]).booleanValue();
    753                CategoryItemLabelGenerator generator
    754                        = getItemLabelGenerator(series, column);
    755                if (generator != null && isItemLabelVisible(series, column)) {
    756                    drawItemLabel(g2, dataset, series, column, plot, generator,
    757                            bar, neg);
    758                }
    759
    760            }
    761        }
    762
    763        /**
    764         * Creates an array of shapes representing the six sides of a block in a
    765         * vertical stack.
    766         *
    767         * @param x0  left edge of bar (in Java2D space).
    768         * @param width  the width of the bar (in Java2D units).
    769         * @param y0  the base of the block (in Java2D space).
    770         * @param y1  the top of the block (in Java2D space).
    771         * @param inverted  a flag indicating whether or not the block is inverted
    772         *     (this changes the order of the faces of the block).
    773         *
    774         * @return The sides of the block.
    775         */
    776        private Shape[] createVerticalBlock(double x0, double width, double y0,
    777                double y1, boolean inverted) {
    778            Shape[] result = new Shape[6];
    779            Point2D p00 = new Point2D.Double(x0, y0);
    780            Point2D p01 = new Point2D.Double(x0 + width, y0);
    781            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
    782                    p01.getY() - getYOffset());
    783            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
    784                    p00.getY() - getYOffset());
    785
    786
    787            Point2D p0 = new Point2D.Double(x0, y1);
    788            Point2D p1 = new Point2D.Double(x0 + width, y1);
    789            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
    790                    p1.getY() - getYOffset());
    791            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
    792                    p0.getY() - getYOffset());
    793
    794            GeneralPath right = new GeneralPath();
    795            right.moveTo((float) p1.getX(), (float) p1.getY());
    796            right.lineTo((float) p01.getX(), (float) p01.getY());
    797            right.lineTo((float) p02.getX(), (float) p02.getY());
    798            right.lineTo((float) p2.getX(), (float) p2.getY());
    799            right.closePath();
    800
    801            GeneralPath left = new GeneralPath();
    802            left.moveTo((float) p0.getX(), (float) p0.getY());
    803            left.lineTo((float) p00.getX(), (float) p00.getY());
    804            left.lineTo((float) p03.getX(), (float) p03.getY());
    805            left.lineTo((float) p3.getX(), (float) p3.getY());
    806            left.closePath();
    807
    808            GeneralPath back = new GeneralPath();
    809            back.moveTo((float) p2.getX(), (float) p2.getY());
    810            back.lineTo((float) p02.getX(), (float) p02.getY());
    811            back.lineTo((float) p03.getX(), (float) p03.getY());
    812            back.lineTo((float) p3.getX(), (float) p3.getY());
    813            back.closePath();
    814
    815            GeneralPath front = new GeneralPath();
    816            front.moveTo((float) p0.getX(), (float) p0.getY());
    817            front.lineTo((float) p1.getX(), (float) p1.getY());
    818            front.lineTo((float) p01.getX(), (float) p01.getY());
    819            front.lineTo((float) p00.getX(), (float) p00.getY());
    820            front.closePath();
    821
    822            GeneralPath top = new GeneralPath();
    823            top.moveTo((float) p0.getX(), (float) p0.getY());
    824            top.lineTo((float) p1.getX(), (float) p1.getY());
    825            top.lineTo((float) p2.getX(), (float) p2.getY());
    826            top.lineTo((float) p3.getX(), (float) p3.getY());
    827            top.closePath();
    828
    829            GeneralPath bottom = new GeneralPath();
    830            bottom.moveTo((float) p00.getX(), (float) p00.getY());
    831            bottom.lineTo((float) p01.getX(), (float) p01.getY());
    832            bottom.lineTo((float) p02.getX(), (float) p02.getY());
    833            bottom.lineTo((float) p03.getX(), (float) p03.getY());
    834            bottom.closePath();
    835
    836            result[0] = bottom;
    837            result[1] = back;
    838            result[2] = left;
    839            result[3] = right;
    840            result[4] = top;
    841            result[5] = front;
    842            if (inverted) {
    843                result[0] = top;
    844                result[4] = bottom;
    845            }
    846            return result;
    847        }
    848
    849        /**
    850         * Tests this renderer for equality with an arbitrary object.
    851         *
    852         * @param obj  the object (<code>null</code> permitted).
    853         *
    854         * @return A boolean.
    855         */
    856        public boolean equals(Object obj) {
    857            if (obj == this) {
    858                return true;
    859            }
    860            if (!(obj instanceof StackedBarRenderer3D)) {
    861                return false;
    862            }
    863            StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
    864            if (this.renderAsPercentages != that.getRenderAsPercentages()) {
    865                return false;
    866            }
    867            if (this.ignoreZeroValues != that.ignoreZeroValues) {
    868                return false;
    869            }
    870            return super.equals(obj);
    871        }
    872
    873        /**
    874         * Returns a hash code for this instance.
    875         *
    876         * @return A hash code.
    877         */
    878        public int hashCode() {
    879            int hash = super.hashCode();
    880            hash = HashUtilities.hashCode(hash, this.renderAsPercentages);
    881            hash = HashUtilities.hashCode(hash, this.ignoreZeroValues);
    882            return hash;
    883        }
    884
    885    }