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