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    }