    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
    091package org.jfree.chart.renderer.category;
    092
    093import java.awt.Color;
    094import java.awt.Graphics2D;
    095import java.awt.Paint;
    096import java.awt.Shape;
    097import java.awt.geom.GeneralPath;
    098import java.awt.geom.Point2D;
    099import java.awt.geom.Rectangle2D;
    100import java.io.Serializable;
    101import java.util.ArrayList;
    102import java.util.List;
    103
    104import org.jfree.chart.HashUtilities;
    105import org.jfree.chart.axis.CategoryAxis;
    106import org.jfree.chart.axis.ValueAxis;
    107import org.jfree.chart.entity.EntityCollection;
    108import org.jfree.chart.event.RendererChangeEvent;
    109import org.jfree.chart.labels.CategoryItemLabelGenerator;
    110import org.jfree.chart.plot.CategoryPlot;
    111import org.jfree.chart.plot.PlotOrientation;
    112import org.jfree.data.DataUtilities;
    113import org.jfree.data.Range;
    114import org.jfree.data.category.CategoryDataset;
    115import org.jfree.data.general.DatasetUtilities;
    116import org.jfree.util.BooleanUtilities;
    117import 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 */
    128public 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}