001/* ===========================================================
002 * JFreeChart : a free chart library for the Java(tm) platform
003 * ===========================================================
004 *
005 * (C) Copyright 2000-2014, 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-2014, 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 *                   DaveLaw (dave ATT davelaw DOTT de) (patch 3204823);
038 *
039 * Changes
040 * -------
041 * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
042 * 15-Nov-2001 : Modified to allow for null data values (DG);
043 * 13-Dec-2001 : Added tooltips (DG);
044 * 15-Feb-2002 : Added isStacked() method (DG);
045 * 24-May-2002 : Incorporated tooltips into chart entities (DG);
046 * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
047 * 25-Jun-2002 : Removed redundant imports (DG);
048 * 26-Jun-2002 : Small change to entity (DG);
049 * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
050 *               for HTML image maps (RA);
051 * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
052 * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
053 *               CategoryToolTipGenerator interface (DG);
054 * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
055 * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
056 * 17-Jan-2003 : Moved plot classes to a separate package (DG);
057 * 25-Mar-2003 : Implemented Serializable (DG);
058 * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
059 *               726260) (DG);
060 * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
061 *               --> StackedBarRenderer3D (DG);
062 * 30-Jul-2003 : Modified entity constructor (CZ);
063 * 07-Oct-2003 : Added renderer state (DG);
064 * 21-Nov-2003 : Added a new constructor (DG);
065 * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
066 * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
067 * 05-Nov-2004 : Modified drawItem() signature (DG);
068 * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
069 * 18-Mar-2005 : Override for getPassCount() method (DG);
070 * 20-Apr-2005 : Renamed CategoryLabelGenerator
071 *               --> CategoryItemLabelGenerator (DG);
072 * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
073 * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
074 * ------------- JFREECHART 1.0.x ---------------------------------------------
075 * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
076 *               by Max Herfort (DG);
077 * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
078 * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList()
079 *               method (DG);
080 * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
081 *               see bug report 1599652 (DG);
082 * 08-May-2007 : Fixed bugs 1713401 (drawBarOutlines flag) and  1713474
083 *               (shading) (DG);
084 * 15-Aug-2008 : Fixed bug 2031407 - no negative zero for stack encoding (DG);
085 * 03-Feb-2009 : Fixed regression in findRangeBounds() method for null
086 *               dataset (DG);
087 * 04-Feb-2009 : Handle seriesVisible flag (DG);
088 * 07-Jul-2009 : Added flag for handling zero values (DG);
089 * 11-Jun-2012 : Use new PaintAlpha class (patch 3204823 from DaveLaw) (DG);
090 *
091 */
092
093package org.jfree.chart.renderer.category;
094
095import java.awt.Graphics2D;
096import java.awt.Paint;
097import java.awt.Shape;
098import java.awt.geom.GeneralPath;
099import java.awt.geom.Point2D;
100import java.awt.geom.Rectangle2D;
101import java.io.Serializable;
102import java.util.ArrayList;
103import java.util.List;
104
105import org.jfree.chart.HashUtilities;
106import org.jfree.chart.axis.CategoryAxis;
107import org.jfree.chart.axis.ValueAxis;
108import org.jfree.chart.entity.EntityCollection;
109import org.jfree.chart.event.RendererChangeEvent;
110import org.jfree.chart.labels.CategoryItemLabelGenerator;
111import org.jfree.chart.plot.CategoryPlot;
112import org.jfree.chart.plot.PlotOrientation;
113import org.jfree.chart.util.PaintAlpha;
114import org.jfree.data.DataUtilities;
115import org.jfree.data.Range;
116import org.jfree.data.category.CategoryDataset;
117import org.jfree.data.general.DatasetUtilities;
118import org.jfree.util.BooleanUtilities;
119import org.jfree.util.PublicCloneable;
120
121/**
122 * Renders stacked bars with 3D-effect, for use with the {@link CategoryPlot}
123 * class.  The example shown here is generated by the
124 * <code>StackedBarChart3DDemo1.java</code> program included in the
125 * JFreeChart Demo Collection:
126 * <br><br>
127 * <img src="../../../../../images/StackedBarRenderer3DSample.png"
128 * alt="StackedBarRenderer3DSample.png">
129 */
130public class StackedBarRenderer3D extends BarRenderer3D
131        implements Cloneable, PublicCloneable, Serializable {
132
133    /** For serialization. */
134    private static final long serialVersionUID = -5832945916493247123L;
135
136    /** A flag that controls whether the bars display values or percentages. */
137    private boolean renderAsPercentages;
138
139    /**
140     * A flag that controls whether or not zero values are drawn by the
141     * renderer.
142     *
143     * @since 1.0.14
144     */
145    private boolean ignoreZeroValues;
146
147    /**
148     * Creates a new renderer with no tool tip generator and no URL generator.
149     * <P>
150     * The defaults (no tool tip or URL generators) have been chosen to
151     * minimise the processing required to generate a default chart.  If you
152     * require tool tips or URLs, then you can easily add the required
153     * generators.
154     */
155    public StackedBarRenderer3D() {
156        this(false);
157    }
158
159    /**
160     * Constructs a new renderer with the specified '3D effect'.
161     *
162     * @param xOffset  the x-offset for the 3D effect.
163     * @param yOffset  the y-offset for the 3D effect.
164     */
165    public StackedBarRenderer3D(double xOffset, double yOffset) {
166        super(xOffset, yOffset);
167    }
168
169    /**
170     * Creates a new renderer.
171     *
172     * @param renderAsPercentages  a flag that controls whether the data values
173     *                             are rendered as percentages.
174     *
175     * @since 1.0.2
176     */
177    public StackedBarRenderer3D(boolean renderAsPercentages) {
178        super();
179        this.renderAsPercentages = renderAsPercentages;
180    }
181
182    /**
183     * Constructs a new renderer with the specified '3D effect'.
184     *
185     * @param xOffset  the x-offset for the 3D effect.
186     * @param yOffset  the y-offset for the 3D effect.
187     * @param renderAsPercentages  a flag that controls whether the data values
188     *                             are rendered as percentages.
189     *
190     * @since 1.0.2
191     */
192    public StackedBarRenderer3D(double xOffset, double yOffset,
193            boolean renderAsPercentages) {
194        super(xOffset, yOffset);
195        this.renderAsPercentages = renderAsPercentages;
196    }
197
198    /**
199     * Returns <code>true</code> if the renderer displays each item value as
200     * a percentage (so that the stacked bars add to 100%), and
201     * <code>false</code> otherwise.
202     *
203     * @return A boolean.
204     *
205     * @since 1.0.2
206     */
207    public boolean getRenderAsPercentages() {
208        return this.renderAsPercentages;
209    }
210
211    /**
212     * Sets the flag that controls whether the renderer displays each item
213     * value as a percentage (so that the stacked bars add to 100%), and sends
214     * a {@link RendererChangeEvent} to all registered listeners.
215     *
216     * @param asPercentages  the flag.
217     *
218     * @since 1.0.2
219     */
220    public void setRenderAsPercentages(boolean asPercentages) {
221        this.renderAsPercentages = asPercentages;
222        fireChangeEvent();
223    }
224
225    /**
226     * Returns the flag that controls whether or not zero values are drawn
227     * by the renderer.
228     *
229     * @return A boolean.
230     *
231     * @since 1.0.14
232     */
233    public boolean getIgnoreZeroValues() {
234        return this.ignoreZeroValues;
235    }
236
237    /**
238     * Sets a flag that controls whether or not zero values are drawn by the
239     * renderer, and sends a {@link RendererChangeEvent} to all registered
240     * listeners.
241     *
242     * @param ignore  the new flag value.
243     *
244     * @since 1.0.14
245     */
246    public void setIgnoreZeroValues(boolean ignore) {
247        this.ignoreZeroValues = ignore;
248        notifyListeners(new RendererChangeEvent(this));
249    }
250
251    /**
252     * Returns the range of values the renderer requires to display all the
253     * items from the specified dataset.
254     *
255     * @param dataset  the dataset (<code>null</code> not permitted).
256     *
257     * @return The range (or <code>null</code> if the dataset is empty).
258     */
259    @Override
260    public Range findRangeBounds(CategoryDataset dataset) {
261        if (dataset == null) {
262            return null;
263        }
264        if (this.renderAsPercentages) {
265            return new Range(0.0, 1.0);
266        }
267        return DatasetUtilities.findStackedRangeBounds(dataset);
268    }
269
270    /**
271     * Calculates the bar width and stores it in the renderer state.
272     *
273     * @param plot  the plot.
274     * @param dataArea  the data area.
275     * @param rendererIndex  the renderer index.
276     * @param state  the renderer state.
277     */
278    @Override
279    protected void calculateBarWidth(CategoryPlot plot, Rectangle2D dataArea,
280            int rendererIndex, 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    @Override
421    public void drawItem(Graphics2D g2, CategoryItemRendererState state,
422            Rectangle2D dataArea, CategoryPlot plot, CategoryAxis domainAxis,
423            ValueAxis rangeAxis, CategoryDataset dataset, int row, int column,
424            int pass) {
425
426        // wait till we are at the last item for the row then draw the
427        // whole stack at once
428        if (row < dataset.getRowCount() - 1) {
429            return;
430        }
431        Comparable category = dataset.getColumnKey(column);
432
433        List values = createStackedValueList(dataset,
434                dataset.getColumnKey(column), state.getVisibleSeriesArray(),
435                getBase(), this.renderAsPercentages);
436
437        Rectangle2D adjusted = new Rectangle2D.Double(dataArea.getX(),
438                dataArea.getY() + getYOffset(),
439                dataArea.getWidth() - getXOffset(),
440                dataArea.getHeight() - getYOffset());
441
442
443        PlotOrientation orientation = plot.getOrientation();
444
445        // handle rendering separately for the two plot orientations...
446        if (orientation == PlotOrientation.HORIZONTAL) {
447            drawStackHorizontal(values, category, g2, state, adjusted, plot,
448                    domainAxis, rangeAxis, dataset);
449        }
450        else {
451            drawStackVertical(values, category, g2, state, adjusted, plot,
452                    domainAxis, rangeAxis, dataset);
453        }
454
455    }
456
457    /**
458     * Draws a stack of bars for one category, with a horizontal orientation.
459     *
460     * @param values  the value list.
461     * @param category  the category.
462     * @param g2  the graphics device.
463     * @param state  the state.
464     * @param dataArea  the data area (adjusted for the 3D effect).
465     * @param plot  the plot.
466     * @param domainAxis  the domain axis.
467     * @param rangeAxis  the range axis.
468     * @param dataset  the dataset.
469     *
470     * @since 1.0.4
471     */
472    protected void drawStackHorizontal(List values, Comparable category,
473            Graphics2D g2, CategoryItemRendererState state,
474            Rectangle2D dataArea, CategoryPlot plot,
475            CategoryAxis domainAxis, ValueAxis rangeAxis,
476            CategoryDataset dataset) {
477
478        int column = dataset.getColumnIndex(category);
479        double barX0 = domainAxis.getCategoryMiddle(column,
480                dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
481                - state.getBarWidth() / 2.0;
482        double barW = state.getBarWidth();
483
484        // a list to store the series index and bar region, so we can draw
485        // all the labels at the end...
486        List itemLabelList = new ArrayList();
487
488        // draw the blocks
489        boolean inverted = rangeAxis.isInverted();
490        int blockCount = values.size() - 1;
491        for (int k = 0; k < blockCount; k++) {
492            int index = (inverted ? blockCount - k - 1 : k);
493            Object[] prev = (Object[]) values.get(index);
494            Object[] curr = (Object[]) values.get(index + 1);
495            int series;
496            if (curr[0] == null) {
497                series = -((Integer) prev[0]).intValue() - 1;
498            }
499            else {
500                series = ((Integer) curr[0]).intValue();
501                if (series < 0) {
502                    series = -((Integer) prev[0]).intValue() - 1;
503                }
504            }
505            double v0 = ((Double) prev[1]).doubleValue();
506            double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
507                    plot.getRangeAxisEdge());
508
509            double v1 = ((Double) curr[1]).doubleValue();
510            double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
511                    plot.getRangeAxisEdge());
512
513            Shape[] faces = createHorizontalBlock(barX0, barW, vv0, vv1,
514                    inverted);
515            Paint fillPaint = getItemPaint(series, column);
516            Paint fillPaintDark = PaintAlpha.darker(fillPaint);
517            boolean drawOutlines = isDrawBarOutline();
518            Paint outlinePaint = fillPaint;
519            if (drawOutlines) {
520                outlinePaint = getItemOutlinePaint(series, column);
521                g2.setStroke(getItemOutlineStroke(series, column));
522            }
523            for (int f = 0; f < 6; f++) {
524                if (f == 5) {
525                    g2.setPaint(fillPaint);
526                }
527                else {
528                    g2.setPaint(fillPaintDark);
529                }
530                g2.fill(faces[f]);
531                if (drawOutlines) {
532                    g2.setPaint(outlinePaint);
533                    g2.draw(faces[f]);
534                }
535            }
536
537            itemLabelList.add(new Object[] {new Integer(series),
538                    faces[5].getBounds2D(),
539                    BooleanUtilities.valueOf(v0 < getBase())});
540
541            // add an item entity, if this information is being collected
542            EntityCollection entities = state.getEntityCollection();
543            if (entities != null) {
544                addItemEntity(entities, dataset, series, column, faces[5]);
545            }
546
547        }
548
549        for (int i = 0; i < itemLabelList.size(); i++) {
550            Object[] record = (Object[]) itemLabelList.get(i);
551            int series = ((Integer) record[0]).intValue();
552            Rectangle2D bar = (Rectangle2D) record[1];
553            boolean neg = ((Boolean) record[2]).booleanValue();
554            CategoryItemLabelGenerator generator
555                    = getItemLabelGenerator(series, column);
556            if (generator != null && isItemLabelVisible(series, column)) {
557                drawItemLabel(g2, dataset, series, column, plot, generator,
558                        bar, neg);
559            }
560
561        }
562    }
563
564    /**
565     * Creates an array of shapes representing the six sides of a block in a
566     * horizontal stack.
567     *
568     * @param x0  left edge of bar (in Java2D space).
569     * @param width  the width of the bar (in Java2D units).
570     * @param y0  the base of the block (in Java2D space).
571     * @param y1  the top of the block (in Java2D space).
572     * @param inverted  a flag indicating whether or not the block is inverted
573     *     (this changes the order of the faces of the block).
574     *
575     * @return The sides of the block.
576     */
577    private Shape[] createHorizontalBlock(double x0, double width, double y0,
578            double y1, boolean inverted) {
579        Shape[] result = new Shape[6];
580        Point2D p00 = new Point2D.Double(y0, x0);
581        Point2D p01 = new Point2D.Double(y0, x0 + width);
582        Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
583                p01.getY() - getYOffset());
584        Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
585                p00.getY() - getYOffset());
586
587        Point2D p0 = new Point2D.Double(y1, x0);
588        Point2D p1 = new Point2D.Double(y1, x0 + width);
589        Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
590                p1.getY() - getYOffset());
591        Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
592                p0.getY() - getYOffset());
593
594        GeneralPath bottom = new GeneralPath();
595        bottom.moveTo((float) p1.getX(), (float) p1.getY());
596        bottom.lineTo((float) p01.getX(), (float) p01.getY());
597        bottom.lineTo((float) p02.getX(), (float) p02.getY());
598        bottom.lineTo((float) p2.getX(), (float) p2.getY());
599        bottom.closePath();
600
601        GeneralPath top = new GeneralPath();
602        top.moveTo((float) p0.getX(), (float) p0.getY());
603        top.lineTo((float) p00.getX(), (float) p00.getY());
604        top.lineTo((float) p03.getX(), (float) p03.getY());
605        top.lineTo((float) p3.getX(), (float) p3.getY());
606        top.closePath();
607
608        GeneralPath back = new GeneralPath();
609        back.moveTo((float) p2.getX(), (float) p2.getY());
610        back.lineTo((float) p02.getX(), (float) p02.getY());
611        back.lineTo((float) p03.getX(), (float) p03.getY());
612        back.lineTo((float) p3.getX(), (float) p3.getY());
613        back.closePath();
614
615        GeneralPath front = new GeneralPath();
616        front.moveTo((float) p0.getX(), (float) p0.getY());
617        front.lineTo((float) p1.getX(), (float) p1.getY());
618        front.lineTo((float) p01.getX(), (float) p01.getY());
619        front.lineTo((float) p00.getX(), (float) p00.getY());
620        front.closePath();
621
622        GeneralPath left = new GeneralPath();
623        left.moveTo((float) p0.getX(), (float) p0.getY());
624        left.lineTo((float) p1.getX(), (float) p1.getY());
625        left.lineTo((float) p2.getX(), (float) p2.getY());
626        left.lineTo((float) p3.getX(), (float) p3.getY());
627        left.closePath();
628
629        GeneralPath right = new GeneralPath();
630        right.moveTo((float) p00.getX(), (float) p00.getY());
631        right.lineTo((float) p01.getX(), (float) p01.getY());
632        right.lineTo((float) p02.getX(), (float) p02.getY());
633        right.lineTo((float) p03.getX(), (float) p03.getY());
634        right.closePath();
635        result[0] = bottom;
636        result[1] = back;
637        if (inverted) {
638            result[2] = right;
639            result[3] = left;
640        }
641        else {
642            result[2] = left;
643            result[3] = right;
644        }
645        result[4] = top;
646        result[5] = front;
647        return result;
648    }
649
650    /**
651     * Draws a stack of bars for one category, with a vertical orientation.
652     *
653     * @param values  the value list.
654     * @param category  the category.
655     * @param g2  the graphics device.
656     * @param state  the state.
657     * @param dataArea  the data area (adjusted for the 3D effect).
658     * @param plot  the plot.
659     * @param domainAxis  the domain axis.
660     * @param rangeAxis  the range axis.
661     * @param dataset  the dataset.
662     *
663     * @since 1.0.4
664     */
665    protected void drawStackVertical(List values, Comparable category,
666            Graphics2D g2, CategoryItemRendererState state,
667            Rectangle2D dataArea, CategoryPlot plot,
668            CategoryAxis domainAxis, ValueAxis rangeAxis,
669            CategoryDataset dataset) {
670
671        int column = dataset.getColumnIndex(category);
672        double barX0 = domainAxis.getCategoryMiddle(column,
673                dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge())
674                - state.getBarWidth() / 2.0;
675        double barW = state.getBarWidth();
676
677        // a list to store the series index and bar region, so we can draw
678        // all the labels at the end...
679        List itemLabelList = new ArrayList();
680
681        // draw the blocks
682        boolean inverted = rangeAxis.isInverted();
683        int blockCount = values.size() - 1;
684        for (int k = 0; k < blockCount; k++) {
685            int index = (inverted ? blockCount - k - 1 : k);
686            Object[] prev = (Object[]) values.get(index);
687            Object[] curr = (Object[]) values.get(index + 1);
688            int series;
689            if (curr[0] == null) {
690                series = -((Integer) prev[0]).intValue() - 1;
691            }
692            else {
693                series = ((Integer) curr[0]).intValue();
694                if (series < 0) {
695                    series = -((Integer) prev[0]).intValue() - 1;
696                }
697            }
698            double v0 = ((Double) prev[1]).doubleValue();
699            double vv0 = rangeAxis.valueToJava2D(v0, dataArea,
700                    plot.getRangeAxisEdge());
701
702            double v1 = ((Double) curr[1]).doubleValue();
703            double vv1 = rangeAxis.valueToJava2D(v1, dataArea,
704                    plot.getRangeAxisEdge());
705
706            Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1,
707                    inverted);
708            Paint fillPaint = getItemPaint(series, column);
709            Paint fillPaintDark = PaintAlpha.darker(fillPaint);
710            boolean drawOutlines = isDrawBarOutline();
711            Paint outlinePaint = fillPaint;
712            if (drawOutlines) {
713                outlinePaint = getItemOutlinePaint(series, column);
714                g2.setStroke(getItemOutlineStroke(series, column));
715            }
716
717            for (int f = 0; f < 6; f++) {
718                if (f == 5) {
719                    g2.setPaint(fillPaint);
720                }
721                else {
722                    g2.setPaint(fillPaintDark);
723                }
724                g2.fill(faces[f]);
725                if (drawOutlines) {
726                    g2.setPaint(outlinePaint);
727                    g2.draw(faces[f]);
728                }
729            }
730
731            itemLabelList.add(new Object[] {new Integer(series),
732                    faces[5].getBounds2D(),
733                    BooleanUtilities.valueOf(v0 < getBase())});
734
735            // add an item entity, if this information is being collected
736            EntityCollection entities = state.getEntityCollection();
737            if (entities != null) {
738                addItemEntity(entities, dataset, series, column, faces[5]);
739            }
740
741        }
742
743        for (int i = 0; i < itemLabelList.size(); i++) {
744            Object[] record = (Object[]) itemLabelList.get(i);
745            int series = ((Integer) record[0]).intValue();
746            Rectangle2D bar = (Rectangle2D) record[1];
747            boolean neg = ((Boolean) record[2]).booleanValue();
748            CategoryItemLabelGenerator generator
749                    = getItemLabelGenerator(series, column);
750            if (generator != null && isItemLabelVisible(series, column)) {
751                drawItemLabel(g2, dataset, series, column, plot, generator,
752                        bar, neg);
753            }
754
755        }
756    }
757
758    /**
759     * Creates an array of shapes representing the six sides of a block in a
760     * vertical stack.
761     *
762     * @param x0  left edge of bar (in Java2D space).
763     * @param width  the width of the bar (in Java2D units).
764     * @param y0  the base of the block (in Java2D space).
765     * @param y1  the top of the block (in Java2D space).
766     * @param inverted  a flag indicating whether or not the block is inverted
767     *     (this changes the order of the faces of the block).
768     *
769     * @return The sides of the block.
770     */
771    private Shape[] createVerticalBlock(double x0, double width, double y0,
772            double y1, boolean inverted) {
773        Shape[] result = new Shape[6];
774        Point2D p00 = new Point2D.Double(x0, y0);
775        Point2D p01 = new Point2D.Double(x0 + width, y0);
776        Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(),
777                p01.getY() - getYOffset());
778        Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(),
779                p00.getY() - getYOffset());
780
781
782        Point2D p0 = new Point2D.Double(x0, y1);
783        Point2D p1 = new Point2D.Double(x0 + width, y1);
784        Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(),
785                p1.getY() - getYOffset());
786        Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(),
787                p0.getY() - getYOffset());
788
789        GeneralPath right = new GeneralPath();
790        right.moveTo((float) p1.getX(), (float) p1.getY());
791        right.lineTo((float) p01.getX(), (float) p01.getY());
792        right.lineTo((float) p02.getX(), (float) p02.getY());
793        right.lineTo((float) p2.getX(), (float) p2.getY());
794        right.closePath();
795
796        GeneralPath left = new GeneralPath();
797        left.moveTo((float) p0.getX(), (float) p0.getY());
798        left.lineTo((float) p00.getX(), (float) p00.getY());
799        left.lineTo((float) p03.getX(), (float) p03.getY());
800        left.lineTo((float) p3.getX(), (float) p3.getY());
801        left.closePath();
802
803        GeneralPath back = new GeneralPath();
804        back.moveTo((float) p2.getX(), (float) p2.getY());
805        back.lineTo((float) p02.getX(), (float) p02.getY());
806        back.lineTo((float) p03.getX(), (float) p03.getY());
807        back.lineTo((float) p3.getX(), (float) p3.getY());
808        back.closePath();
809
810        GeneralPath front = new GeneralPath();
811        front.moveTo((float) p0.getX(), (float) p0.getY());
812        front.lineTo((float) p1.getX(), (float) p1.getY());
813        front.lineTo((float) p01.getX(), (float) p01.getY());
814        front.lineTo((float) p00.getX(), (float) p00.getY());
815        front.closePath();
816
817        GeneralPath top = new GeneralPath();
818        top.moveTo((float) p0.getX(), (float) p0.getY());
819        top.lineTo((float) p1.getX(), (float) p1.getY());
820        top.lineTo((float) p2.getX(), (float) p2.getY());
821        top.lineTo((float) p3.getX(), (float) p3.getY());
822        top.closePath();
823
824        GeneralPath bottom = new GeneralPath();
825        bottom.moveTo((float) p00.getX(), (float) p00.getY());
826        bottom.lineTo((float) p01.getX(), (float) p01.getY());
827        bottom.lineTo((float) p02.getX(), (float) p02.getY());
828        bottom.lineTo((float) p03.getX(), (float) p03.getY());
829        bottom.closePath();
830
831        result[0] = bottom;
832        result[1] = back;
833        result[2] = left;
834        result[3] = right;
835        result[4] = top;
836        result[5] = front;
837        if (inverted) {
838            result[0] = top;
839            result[4] = bottom;
840        }
841        return result;
842    }
843
844    /**
845     * Tests this renderer for equality with an arbitrary object.
846     *
847     * @param obj  the object (<code>null</code> permitted).
848     *
849     * @return A boolean.
850     */
851    @Override
852    public boolean equals(Object obj) {
853        if (obj == this) {
854            return true;
855        }
856        if (!(obj instanceof StackedBarRenderer3D)) {
857            return false;
858        }
859        StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
860        if (this.renderAsPercentages != that.getRenderAsPercentages()) {
861            return false;
862        }
863        if (this.ignoreZeroValues != that.ignoreZeroValues) {
864            return false;
865        }
866        return super.equals(obj);
867    }
868
869    /**
870     * Returns a hash code for this instance.
871     * 
872     * @return A hash code.
873     */
874    @Override
875    public int hashCode() {
876        int hash = super.hashCode();
877        hash = HashUtilities.hashCode(hash, this.renderAsPercentages);
878        hash = HashUtilities.hashCode(hash, this.ignoreZeroValues);
879        return hash;
880    }
881
882}