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