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
086package org.jfree.chart.renderer.category;
087
088import java.awt.Color;
089import java.awt.Graphics2D;
090import java.awt.Paint;
091import java.awt.Shape;
092import java.awt.geom.GeneralPath;
093import java.awt.geom.Point2D;
094import java.awt.geom.Rectangle2D;
095import java.io.Serializable;
096import java.util.ArrayList;
097import java.util.List;
098
099import org.jfree.chart.axis.CategoryAxis;
100import org.jfree.chart.axis.ValueAxis;
101import org.jfree.chart.entity.EntityCollection;
102import org.jfree.chart.event.RendererChangeEvent;
103import org.jfree.chart.labels.CategoryItemLabelGenerator;
104import org.jfree.chart.plot.CategoryPlot;
105import org.jfree.chart.plot.PlotOrientation;
106import org.jfree.data.DataUtilities;
107import org.jfree.data.Range;
108import org.jfree.data.category.CategoryDataset;
109import org.jfree.data.general.DatasetUtilities;
110import org.jfree.util.BooleanUtilities;
111import 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 */
117public 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        notifyListeners(new RendererChangeEvent(this));
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}