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