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