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