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