    001    /* ===========================================================
    002     * JFreeChart : a free chart library for the Java(tm) platform
    003     * ===========================================================
    004     *
    005     * (C) Copyright 2000-2005, 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-2005, 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     *
    037     * $Id: StackedBarRenderer3D.java,v 1.8.2.3 2005/10/25 20:54:16 mungady Exp $
    038     *
    039     * Changes
    040     * -------
    041     * 31-Oct-2001 : Version 1, contributed by Serge V. Grachov (DG);
    042     * 15-Nov-2001 : Modified to allow for null data values (DG);
    043     * 13-Dec-2001 : Added tooltips (DG);
    044     * 15-Feb-2002 : Added isStacked() method (DG);
    045     * 24-May-2002 : Incorporated tooltips into chart entities (DG);
    046     * 19-Jun-2002 : Added check for null info in drawCategoryItem method (DG);
    047     * 25-Jun-2002 : Removed redundant imports (DG);
    048     * 26-Jun-2002 : Small change to entity (DG);
    049     * 05-Aug-2002 : Small modification to drawCategoryItem method to support URLs
    050     *               for HTML image maps (RA);
    051     * 26-Sep-2002 : Fixed errors reported by Checkstyle (DG);
    052     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and
    053     *               CategoryToolTipGenerator interface (DG);
    054     * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
    055     * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
    056     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
    057     * 25-Mar-2003 : Implemented Serializable (DG);
    058     * 01-May-2003 : Added default constructor (bug 726235) and fixed bug
    059     *               726260) (DG);
    060     * 13-May-2003 : Renamed StackedVerticalBarRenderer3D
    061     *               --> StackedBarRenderer3D (DG);
    062     * 30-Jul-2003 : Modified entity constructor (CZ);
    063     * 07-Oct-2003 : Added renderer state (DG);
    064     * 21-Nov-2003 : Added a new constructor (DG);
    065     * 27-Nov-2003 : Modified code to respect maxBarWidth setting (DG);
    066     * 11-Aug-2004 : Fixed bug where isDrawBarOutline() was ignored (DG);
    067     * 05-Nov-2004 : Modified drawItem() signature (DG);
    068     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds (DG);
    069     * 18-Mar-2005 : Override for getPassCount() method (DG);
    070     * 20-Apr-2005 : Renamed CategoryLabelGenerator
    071     *               --> CategoryItemLabelGenerator (DG);
    072     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
    073     * 22-Sep-2005 : Renamed getMaxBarWidth() --> getMaximumBarWidth() (DG);
    074     *
    075     */
    076
    077    package org.jfree.chart.renderer.category;
    078
    079    import java.awt.Color;
    080    import java.awt.Graphics2D;
    081    import java.awt.Paint;
    082    import java.awt.geom.GeneralPath;
    083    import java.awt.geom.Rectangle2D;
    084    import java.io.Serializable;
    085
    086    import org.jfree.chart.axis.CategoryAxis;
    087    import org.jfree.chart.axis.ValueAxis;
    088    import org.jfree.chart.entity.EntityCollection;
    089    import org.jfree.chart.labels.CategoryItemLabelGenerator;
    090    import org.jfree.chart.plot.CategoryPlot;
    091    import org.jfree.chart.plot.PlotOrientation;
    092    import org.jfree.data.Range;
    093    import org.jfree.data.category.CategoryDataset;
    094    import org.jfree.data.general.DatasetUtilities;
    095    import org.jfree.ui.RectangleEdge;
    096    import org.jfree.util.PublicCloneable;
    097
    098    /**
    099     * Renders stacked bars with 3D-effect, for use with the
    100     * {@link org.jfree.chart.plot.CategoryPlot} class.
    101     */
    102    public class StackedBarRenderer3D extends BarRenderer3D
    103                                      implements Cloneable, PublicCloneable,
    104                                                 Serializable {
    105
    106        /** For serialization. */
    107        private static final long serialVersionUID = -5832945916493247123L;
    108
    109        /**
    110         * Creates a new renderer with no tool tip generator and no URL generator.
    111         * <P>
    112         * The defaults (no tool tip or URL generators) have been chosen to
    113         * minimise the processing required to generate a default chart.  If you
    114         * require tool tips or URLs, then you can easily add the required
    115         * generators.
    116         */
    117        public StackedBarRenderer3D() {
    118            super();
    119        }
    120
    121        /**
    122         * Constructs a new renderer with the specified '3D effect'.
    123         *
    124         * @param xOffset  the x-offset for the 3D effect.
    125         * @param yOffset  the y-offset for the 3D effect.
    126         */
    127        public StackedBarRenderer3D(double xOffset, double yOffset) {
    128            super(xOffset, yOffset);
    129        }
    130
    131        /**
    132         * Returns the range of values the renderer requires to display all the
    133         * items from the specified dataset.
    134         *
    135         * @param dataset  the dataset (<code>null</code> not permitted).
    136         *
    137         * @return The range (or <code>null</code> if the dataset is empty).
    138         */
    139        public Range findRangeBounds(CategoryDataset dataset) {
    140            return DatasetUtilities.findStackedRangeBounds(dataset);
    141        }
    142
    143        /**
    144         * Calculates the bar width and stores it in the renderer state.
    145         *
    146         * @param plot  the plot.
    147         * @param dataArea  the data area.
    148         * @param rendererIndex  the renderer index.
    149         * @param state  the renderer state.
    150         */
    151        protected void calculateBarWidth(CategoryPlot plot,
    152                                         Rectangle2D dataArea,
    153                                         int rendererIndex,
    154                                         CategoryItemRendererState state) {
    155
    156            // calculate the bar width
    157            CategoryAxis domainAxis = getDomainAxis(plot, rendererIndex);
    158            CategoryDataset data = plot.getDataset(rendererIndex);
    159            if (data != null) {
    160                PlotOrientation orientation = plot.getOrientation();
    161                double space = 0.0;
    162                if (orientation == PlotOrientation.HORIZONTAL) {
    163                    space = dataArea.getHeight();
    164                }
    165                else if (orientation == PlotOrientation.VERTICAL) {
    166                    space = dataArea.getWidth();
    167                }
    168                double maxWidth = space * getMaximumBarWidth();
    169                int columns = data.getColumnCount();
    170                double categoryMargin = 0.0;
    171                if (columns > 1) {
    172                    categoryMargin = domainAxis.getCategoryMargin();
    173                }
    174
    175                double used = space * (1 - domainAxis.getLowerMargin()
    176                                         - domainAxis.getUpperMargin()
    177                                         - categoryMargin);
    178                if (columns > 0) {
    179                    state.setBarWidth(Math.min(used / columns, maxWidth));
    180                }
    181                else {
    182                    state.setBarWidth(Math.min(used, maxWidth));
    183                }
    184            }
    185
    186        }
    187
    188        /**
    189         * Draws a stacked bar (with 3D-effect) for a specific item.
    190         *
    191         * @param g2  the graphics device.
    192         * @param state  the renderer state.
    193         * @param dataArea  the plot area.
    194         * @param plot  the plot.
    195         * @param domainAxis  the domain (category) axis.
    196         * @param rangeAxis  the range (value) axis.
    197         * @param dataset  the data.
    198         * @param row  the row index (zero-based).
    199         * @param column  the column index (zero-based).
    200         * @param pass  the pass index.
    201         */
    202        public void drawItem(Graphics2D g2,
    203                             CategoryItemRendererState state,
    204                             Rectangle2D dataArea,
    205                             CategoryPlot plot,
    206                             CategoryAxis domainAxis,
    207                             ValueAxis rangeAxis,
    208                             CategoryDataset dataset,
    209                             int row,
    210                             int column,
    211                             int pass) {
    212
    213            // check the value we are plotting...
    214            Number dataValue = dataset.getValue(row, column);
    215            if (dataValue == null) {
    216                return;
    217            }
    218
    219            double value = dataValue.doubleValue();
    220
    221            Rectangle2D adjusted = new Rectangle2D.Double(
    222                dataArea.getX(), dataArea.getY() + getYOffset(),
    223                dataArea.getWidth() - getXOffset(),
    224                dataArea.getHeight() - getYOffset()
    225            );
    226
    227            PlotOrientation orientation = plot.getOrientation();
    228
    229            double barW0 = domainAxis.getCategoryMiddle(
    230                column, getColumnCount(), adjusted, plot.getDomainAxisEdge()
    231            ) - state.getBarWidth() / 2.0;
    232
    233            double positiveBase = getBase();
    234            double negativeBase = positiveBase;
    235            for (int i = 0; i < row; i++) {
    236                Number v = dataset.getValue(i, column);
    237                if (v != null) {
    238                    double d = v.doubleValue();
    239                    if (d > 0) {
    240                        positiveBase = positiveBase + d;
    241                    }
    242                    else {
    243                        negativeBase = negativeBase + d;
    244                    }
    245                }
    246            }
    247
    248            double translatedBase;
    249            double translatedValue;
    250            RectangleEdge location = plot.getRangeAxisEdge();
    251            if (value > 0.0) {
    252                translatedBase = rangeAxis.valueToJava2D(positiveBase, adjusted,
    253                        location);
    254                translatedValue = rangeAxis.valueToJava2D(positiveBase + value,
    255                        adjusted, location);
    256            }
    257            else {
    258                translatedBase = rangeAxis.valueToJava2D(negativeBase, adjusted,
    259                        location);
    260                translatedValue = rangeAxis.valueToJava2D(negativeBase + value,
    261                        adjusted, location);
    262            }
    263            double barL0 = Math.min(translatedBase, translatedValue);
    264            double barLength = Math.max(
    265                Math.abs(translatedValue - translatedBase), getMinimumBarLength()
    266            );
    267
    268            Rectangle2D bar = null;
    269            if (orientation == PlotOrientation.HORIZONTAL) {
    270                bar = new Rectangle2D.Double(barL0, barW0, barLength,
    271                        state.getBarWidth());
    272            }
    273            else {
    274                bar = new Rectangle2D.Double(barW0, barL0, state.getBarWidth(),
    275                        barLength);
    276            }
    277            Paint itemPaint = getItemPaint(row, column);
    278            g2.setPaint(itemPaint);
    279            g2.fill(bar);
    280
    281            if (pass == 0) {
    282                double x0 = bar.getMinX();
    283                double x1 = x0 + getXOffset();
    284                double x2 = bar.getMaxX();
    285                double x3 = x2 + getXOffset();
    286
    287                double y0 = bar.getMinY() - getYOffset();
    288                double y1 = bar.getMinY();
    289                double y2 = bar.getMaxY() - getYOffset();
    290                double y3 = bar.getMaxY();
    291
    292                GeneralPath bar3dRight = null;
    293                GeneralPath bar3dTop = null;
    294                if (value > 0.0 || orientation == PlotOrientation.VERTICAL) {
    295                    bar3dRight = new GeneralPath();
    296                    bar3dRight.moveTo((float) x2, (float) y3);
    297                    bar3dRight.lineTo((float) x2, (float) y1);
    298                    bar3dRight.lineTo((float) x3, (float) y0);
    299                    bar3dRight.lineTo((float) x3, (float) y2);
    300                    bar3dRight.closePath();
    301
    302                    if (itemPaint instanceof Color) {
    303                        g2.setPaint(((Color) itemPaint).darker());
    304                    }
    305                    g2.fill(bar3dRight);
    306                }
    307
    308                if (value > 0.0 || orientation == PlotOrientation.HORIZONTAL) {
    309                    bar3dTop = new GeneralPath();
    310                    bar3dTop.moveTo((float) x0, (float) y1);
    311                    bar3dTop.lineTo((float) x1, (float) y0);
    312                    bar3dTop.lineTo((float) x3, (float) y0);
    313                    bar3dTop.lineTo((float) x2, (float) y1);
    314                    bar3dTop.closePath();
    315                    g2.fill(bar3dTop);
    316                }
    317
    318                if (isDrawBarOutline() && state.getBarWidth() > 3) {
    319                    g2.setStroke(getItemOutlineStroke(row, column));
    320                    g2.setPaint(getItemOutlinePaint(row, column));
    321                    g2.draw(bar);
    322                    if (bar3dRight != null) {
    323                        g2.draw(bar3dRight);
    324                    }
    325                    if (bar3dTop != null) {
    326                        g2.draw(bar3dTop);
    327                    }
    328                }
    329
    330                // add an item entity, if this information is being collected
    331                EntityCollection entities = state.getEntityCollection();
    332                if (entities != null) {
    333                    addItemEntity(entities, dataset, row, column, bar);
    334                }
    335            }
    336            else if (pass == 1) {
    337                CategoryItemLabelGenerator generator
    338                    = getItemLabelGenerator(row, column);
    339                if (generator != null && isItemLabelVisible(row, column)) {
    340                    drawItemLabel(
    341                        g2, dataset, row, column, plot, generator, bar,
    342                        (value < 0.0)
    343                    );
    344                }
    345            }
    346
    347        }
    348
    349        /**
    350         * Returns the number of passes through the dataset required by the
    351         * renderer.  This method returns <code>2</code>, the second pass is used
    352         * to draw the item labels.
    353         *
    354         * @return The pass count.
    355         */
    356        public int getPassCount() {
    357            return 2;
    358        }
    359
    360    }