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