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