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     * $Id: StackedBarRenderer3D.java,v 1.8.2.8 2007/01/18 14:49:52 mungady Exp $
039     *
040     * Changes
041     * -------
042     * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
043     * 15-Nov-2001 : Modified to allow for null data values (DG);
044     * 13-Dec-2001 : Added tooltips (DG);
045     * 15-Feb-2002 : Added isStacked() method (DG);
046     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
047     * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
048     * 25-Jun-2002 : Removed redundant imports (DG);
049     * 26-Jun-2002 : Small change to entity (DG);
050     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs 
051     *               for HTML image maps (RA);
052     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
053     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
054     *               CategoryToolTipGenerator interface (DG);
055     * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
056     * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
057     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
058     * 25-Mar-2003 : Implemented Serializable (DG);
059     * 01-May-2003 : Added default constructor (bug 726235) and fixed bug 
060     *               726260) (DG);
061     * 13-May-2003 : Renamed StackedVerticalBarRenderer3D 
062     *               --> StackedBarRenderer3D (DG);
063     * 30-Jul-2003 : Modified entity constructor (CZ);
064     * 07-Oct-2003 : Added renderer state (DG);
065     * 21-Nov-2003 : Added a new constructor (DG);
066     * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
067     * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
068     * 05-Nov-2004 : Modified drawItem() signature (DG);
069     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
070     * 18-Mar-2005 : Override for getPassCount() method (DG);
071     * 20-Apr-2005 : Renamed CategoryLabelGenerator 
072     *               --> CategoryItemLabelGenerator (DG);
073     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
074     * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
075     * ------------- JFREECHART 1.0.x ---------------------------------------------
076     * 31-Mar-2006 : Added renderAsPercentages option - see patch 1459313 submitted
077     *               by Max Herfort (DG);
078     * 16-Jan-2007 : Replaced rendering code to draw whole stack at once (DG);
079     * 18-Jan-2007 : Fixed bug handling null values in createStackedValueList() 
080     *               method (DG);
081     * 18-Jan-2007 : Updated block drawing code to take account of inverted axes,
082     *               see bug report 1599652 (DG);
083     *               
084     */
085    
086    package org.jfree.chart.renderer.category;
087    
088    import java.awt.Graphics2D;
089    import java.awt.Paint;
090    import java.awt.Shape;
091    import java.awt.geom.GeneralPath;
092    import java.awt.geom.Point2D;
093    import java.awt.geom.Rectangle2D;
094    import java.io.Serializable;
095    import java.util.ArrayList;
096    import java.util.List;
097    
098    import org.jfree.chart.axis.CategoryAxis;
099    import org.jfree.chart.axis.ValueAxis;
100    import org.jfree.chart.entity.EntityCollection;
101    import org.jfree.chart.event.RendererChangeEvent;
102    import org.jfree.chart.labels.CategoryItemLabelGenerator;
103    import org.jfree.chart.plot.CategoryPlot;
104    import org.jfree.chart.plot.PlotOrientation;
105    import org.jfree.data.DataUtilities;
106    import org.jfree.data.Range;
107    import org.jfree.data.category.CategoryDataset;
108    import org.jfree.data.general.DatasetUtilities;
109    import org.jfree.util.BooleanUtilities;
110    import org.jfree.util.PublicCloneable;
111    
112    /**
113     * Renders stacked bars with 3D-effect, for use with the 
114     * {@link org.jfree.chart.plot.CategoryPlot} class.
115     */
116    public class StackedBarRenderer3D extends BarRenderer3D 
117                                      implements Cloneable, PublicCloneable, 
118                                                 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            notifyListeners(new RendererChangeEvent(this));
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 outlinePaint = getItemOutlinePaint(series, column);
444                g2.setStroke(getItemOutlineStroke(series, column));
445                
446                for (int f = 0; f < 6; f++) {
447                    g2.setPaint(fillPaint);
448                    g2.fill(faces[f]);
449                    g2.setPaint(outlinePaint);
450                    g2.draw(faces[f]); 
451                }
452                            
453                itemLabelList.add(new Object[] {new Integer(series), 
454                        faces[5].getBounds2D(), 
455                        BooleanUtilities.valueOf(v0 < getBase())});
456    
457                // add an item entity, if this information is being collected
458                EntityCollection entities = state.getEntityCollection();
459                if (entities != null) {
460                    addItemEntity(entities, dataset, series, column, faces[5]);
461                }
462    
463            }        
464    
465            for (int i = 0; i < itemLabelList.size(); i++) {
466                Object[] record = (Object[]) itemLabelList.get(i);
467                int series = ((Integer) record[0]).intValue();
468                Rectangle2D bar = (Rectangle2D) record[1];
469                boolean neg = ((Boolean) record[2]).booleanValue();
470                CategoryItemLabelGenerator generator 
471                        = getItemLabelGenerator(series, column);
472                if (generator != null && isItemLabelVisible(series, column)) {
473                    drawItemLabel(g2, dataset, series, column, plot, generator, 
474                            bar, neg);
475                }
476    
477            }
478        }
479        
480        /**
481         * Creates an array of shapes representing the six sides of a block in a
482         * horizontal stack.
483         * 
484         * @param x0  left edge of bar (in Java2D space).
485         * @param width  the width of the bar (in Java2D units).
486         * @param y0  the base of the block (in Java2D space).
487         * @param y1  the top of the block (in Java2D space).
488         * @param inverted  a flag indicating whether or not the block is inverted
489         *     (this changes the order of the faces of the block).
490         * 
491         * @return The sides of the block.
492         */
493        private Shape[] createHorizontalBlock(double x0, double width, double y0, 
494                double y1, boolean inverted) {
495            Shape[] result = new Shape[6];
496            Point2D p00 = new Point2D.Double(y0, x0);
497            Point2D p01 = new Point2D.Double(y0, x0 + width);
498            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
499                    p01.getY() - getYOffset());
500            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
501                    p00.getY() - getYOffset());
502    
503            Point2D p0 = new Point2D.Double(y1, x0);
504            Point2D p1 = new Point2D.Double(y1, x0 + width);
505            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
506                    p1.getY() - getYOffset());
507            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
508                    p0.getY() - getYOffset());
509            
510            GeneralPath bottom = new GeneralPath();
511            bottom.moveTo((float) p1.getX(), (float) p1.getY());
512            bottom.lineTo((float) p01.getX(), (float) p01.getY());
513            bottom.lineTo((float) p02.getX(), (float) p02.getY());
514            bottom.lineTo((float) p2.getX(), (float) p2.getY());
515            bottom.closePath();
516            
517            GeneralPath top = new GeneralPath();
518            top.moveTo((float) p0.getX(), (float) p0.getY());
519            top.lineTo((float) p00.getX(), (float) p00.getY());
520            top.lineTo((float) p03.getX(), (float) p03.getY());
521            top.lineTo((float) p3.getX(), (float) p3.getY());
522            top.closePath();
523    
524            GeneralPath back = new GeneralPath();
525            back.moveTo((float) p2.getX(), (float) p2.getY());
526            back.lineTo((float) p02.getX(), (float) p02.getY());
527            back.lineTo((float) p03.getX(), (float) p03.getY());
528            back.lineTo((float) p3.getX(), (float) p3.getY());
529            back.closePath();
530            
531            GeneralPath front = new GeneralPath();
532            front.moveTo((float) p0.getX(), (float) p0.getY());
533            front.lineTo((float) p1.getX(), (float) p1.getY());
534            front.lineTo((float) p01.getX(), (float) p01.getY());
535            front.lineTo((float) p00.getX(), (float) p00.getY());
536            front.closePath();
537    
538            GeneralPath left = new GeneralPath();
539            left.moveTo((float) p0.getX(), (float) p0.getY());
540            left.lineTo((float) p1.getX(), (float) p1.getY());
541            left.lineTo((float) p2.getX(), (float) p2.getY());
542            left.lineTo((float) p3.getX(), (float) p3.getY());
543            left.closePath();
544            
545            GeneralPath right = new GeneralPath();
546            right.moveTo((float) p00.getX(), (float) p00.getY());
547            right.lineTo((float) p01.getX(), (float) p01.getY());
548            right.lineTo((float) p02.getX(), (float) p02.getY());
549            right.lineTo((float) p03.getX(), (float) p03.getY());
550            right.closePath();
551            result[0] = bottom;
552            result[1] = back;
553            if (inverted) {
554                result[2] = right;
555                result[3] = left;
556            }
557            else {
558                result[2] = left;
559                result[3] = right;
560            }
561            result[4] = top;
562            result[5] = front;
563            return result;
564        }
565        
566        /**
567         * Draws a stack of bars for one category, with a vertical orientation.
568         * 
569         * @param values  the value list.
570         * @param category  the category.
571         * @param g2  the graphics device.
572         * @param state  the state.
573         * @param dataArea  the data area (adjusted for the 3D effect).
574         * @param plot  the plot.
575         * @param domainAxis  the domain axis.
576         * @param rangeAxis  the range axis.
577         * @param dataset  the dataset.
578         *
579         * @since 1.0.4
580         */
581        protected void drawStackVertical(List values, Comparable category, 
582                Graphics2D g2, CategoryItemRendererState state, 
583                Rectangle2D dataArea, CategoryPlot plot, 
584                CategoryAxis domainAxis, ValueAxis rangeAxis, 
585                CategoryDataset dataset) {
586            
587            int column = dataset.getColumnIndex(category);
588            double barX0 = domainAxis.getCategoryMiddle(column, 
589                    dataset.getColumnCount(), dataArea, plot.getDomainAxisEdge()) 
590                    - state.getBarWidth() / 2.0;
591            double barW = state.getBarWidth();
592    
593            // a list to store the series index and bar region, so we can draw
594            // all the labels at the end...
595            List itemLabelList = new ArrayList();
596            
597            // draw the blocks
598            boolean inverted = rangeAxis.isInverted();
599            int blockCount = values.size() - 1;
600            for (int k = 0; k < blockCount; k++) {
601                int index = (inverted ? blockCount - k - 1 : k);
602                Object[] prev = (Object[]) values.get(index);
603                Object[] curr = (Object[]) values.get(index + 1);
604                int series = 0;
605                if (curr[0] == null) {
606                    series = -((Integer) prev[0]).intValue();
607                }
608                else {
609                    series = ((Integer) curr[0]).intValue();
610                    if (series < 0) {
611                        series = -((Integer) prev[0]).intValue();
612                    }
613                }
614                double v0 = ((Double) prev[1]).doubleValue();
615                double vv0 = rangeAxis.valueToJava2D(v0, dataArea, 
616                        plot.getRangeAxisEdge());
617    
618                double v1 = ((Double) curr[1]).doubleValue();
619                double vv1 = rangeAxis.valueToJava2D(v1, dataArea, 
620                        plot.getRangeAxisEdge());
621                
622                Shape[] faces = createVerticalBlock(barX0, barW, vv0, vv1, 
623                        inverted);
624                Paint fillPaint = getItemPaint(series, column);
625                Paint outlinePaint = getItemOutlinePaint(series, column);
626                g2.setStroke(getItemOutlineStroke(series, column));
627                
628                for (int f = 0; f < 6; f++) {
629                    g2.setPaint(fillPaint);
630                    g2.fill(faces[f]);
631                    g2.setPaint(outlinePaint);
632                    g2.draw(faces[f]); 
633                }
634    
635                itemLabelList.add(new Object[] {new Integer(series), 
636                        faces[5].getBounds2D(), 
637                        BooleanUtilities.valueOf(v0 < getBase())});
638                
639                // add an item entity, if this information is being collected
640                EntityCollection entities = state.getEntityCollection();
641                if (entities != null) {
642                    addItemEntity(entities, dataset, series, column, faces[5]);
643                }
644    
645            }
646            
647            for (int i = 0; i < itemLabelList.size(); i++) {
648                Object[] record = (Object[]) itemLabelList.get(i);
649                int series = ((Integer) record[0]).intValue();
650                Rectangle2D bar = (Rectangle2D) record[1];
651                boolean neg = ((Boolean) record[2]).booleanValue();
652                CategoryItemLabelGenerator generator 
653                        = getItemLabelGenerator(series, column);
654                if (generator != null && isItemLabelVisible(series, column)) {
655                    drawItemLabel(g2, dataset, series, column, plot, generator, 
656                            bar, neg);
657                }
658    
659            }
660        }
661        
662        /**
663         * Creates an array of shapes representing the six sides of a block in a
664         * vertical stack.
665         * 
666         * @param x0  left edge of bar (in Java2D space).
667         * @param width  the width of the bar (in Java2D units).
668         * @param y0  the base of the block (in Java2D space).
669         * @param y1  the top of the block (in Java2D space).
670         * @param inverted  a flag indicating whether or not the block is inverted
671         *     (this changes the order of the faces of the block).
672         * 
673         * @return The sides of the block.
674         */
675        private Shape[] createVerticalBlock(double x0, double width, double y0, 
676                double y1, boolean inverted) {
677            Shape[] result = new Shape[6];
678            Point2D p00 = new Point2D.Double(x0, y0);
679            Point2D p01 = new Point2D.Double(x0 + width, y0);
680            Point2D p02 = new Point2D.Double(p01.getX() + getXOffset(), 
681                    p01.getY() - getYOffset());
682            Point2D p03 = new Point2D.Double(p00.getX() + getXOffset(), 
683                    p00.getY() - getYOffset());
684    
685    
686            Point2D p0 = new Point2D.Double(x0, y1);
687            Point2D p1 = new Point2D.Double(x0 + width, y1);
688            Point2D p2 = new Point2D.Double(p1.getX() + getXOffset(), 
689                    p1.getY() - getYOffset());
690            Point2D p3 = new Point2D.Double(p0.getX() + getXOffset(), 
691                    p0.getY() - getYOffset());
692            
693            GeneralPath right = new GeneralPath();
694            right.moveTo((float) p1.getX(), (float) p1.getY());
695            right.lineTo((float) p01.getX(), (float) p01.getY());
696            right.lineTo((float) p02.getX(), (float) p02.getY());
697            right.lineTo((float) p2.getX(), (float) p2.getY());
698            right.closePath();
699            
700            GeneralPath left = new GeneralPath();
701            left.moveTo((float) p0.getX(), (float) p0.getY());
702            left.lineTo((float) p00.getX(), (float) p00.getY());
703            left.lineTo((float) p03.getX(), (float) p03.getY());
704            left.lineTo((float) p3.getX(), (float) p3.getY());
705            left.closePath();
706    
707            GeneralPath back = new GeneralPath();
708            back.moveTo((float) p2.getX(), (float) p2.getY());
709            back.lineTo((float) p02.getX(), (float) p02.getY());
710            back.lineTo((float) p03.getX(), (float) p03.getY());
711            back.lineTo((float) p3.getX(), (float) p3.getY());
712            back.closePath();
713            
714            GeneralPath front = new GeneralPath();
715            front.moveTo((float) p0.getX(), (float) p0.getY());
716            front.lineTo((float) p1.getX(), (float) p1.getY());
717            front.lineTo((float) p01.getX(), (float) p01.getY());
718            front.lineTo((float) p00.getX(), (float) p00.getY());
719            front.closePath();
720    
721            GeneralPath top = new GeneralPath();
722            top.moveTo((float) p0.getX(), (float) p0.getY());
723            top.lineTo((float) p1.getX(), (float) p1.getY());
724            top.lineTo((float) p2.getX(), (float) p2.getY());
725            top.lineTo((float) p3.getX(), (float) p3.getY());
726            top.closePath();
727            
728            GeneralPath bottom = new GeneralPath();
729            bottom.moveTo((float) p00.getX(), (float) p00.getY());
730            bottom.lineTo((float) p01.getX(), (float) p01.getY());
731            bottom.lineTo((float) p02.getX(), (float) p02.getY());
732            bottom.lineTo((float) p03.getX(), (float) p03.getY());
733            bottom.closePath();
734            
735            result[0] = bottom;
736            result[1] = back;
737            result[2] = left;
738            result[3] = right;
739            result[4] = top;
740            result[5] = front;
741            if (inverted) {
742                result[0] = top;
743                result[4] = bottom;
744            }
745            return result;
746        }
747        
748        /**
749         * Tests this renderer for equality with an arbitrary object.
750         * 
751         * @param obj  the object (<code>null</code> permitted).
752         * 
753         * @return A boolean.
754         */
755        public boolean equals(Object obj) {
756            if (obj == this) {
757                return true;   
758            }
759            if (!(obj instanceof StackedBarRenderer3D)) {
760                return false;   
761            }
762            if (!super.equals(obj)) {
763                return false;   
764            }
765            StackedBarRenderer3D that = (StackedBarRenderer3D) obj;
766            if (this.renderAsPercentages != that.getRenderAsPercentages()) {
767                return false;   
768            }
769            return true;
770        }
771    
772    }