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     * StackedAreaRenderer.java
029     * ------------------------
030     * (C) Copyright 2002-2005, by Dan Rivett (d.rivett@ukonline.co.uk) and 
031     *                          Contributors.
032     *
033     * Original Author:  Dan Rivett (adapted from AreaCategoryItemRenderer);
034     * Contributor(s):   Jon Iles;
035     *                   David Gilbert (for Object Refinery Limited);
036     *                   Christian W. Zuckschwerdt;
037     *
038     * $Id: StackedAreaRenderer.java,v 1.6.2.2 2005/10/25 20:54:16 mungady Exp $
039     *
040     * Changes:
041     * --------
042     * 20-Sep-2002 : Version 1, contributed by Dan Rivett;
043     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
044     *               CategoryToolTipGenerator interface (DG);
045     * 01-Nov-2002 : Added tooltips (DG);
046     * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 
047     *               for category spacing. Renamed StackedAreaCategoryItemRenderer 
048     *               --> StackedAreaRenderer (DG);
049     * 26-Nov-2002 : Switched CategoryDataset --> TableDataset (DG);
050     * 26-Nov-2002 : Replaced isStacked() method with getRangeType() method (DG);
051     * 17-Jan-2003 : Moved plot classes to a separate package (DG);
052     * 25-Mar-2003 : Implemented Serializable (DG);
053     * 13-May-2003 : Modified to take into account the plot orientation (DG);
054     * 30-Jul-2003 : Modified entity constructor (CZ);
055     * 07-Oct-2003 : Added renderer state (DG);
056     * 29-Apr-2004 : Added getRangeExtent() override (DG);
057     * 05-Nov-2004 : Modified drawItem() signature (DG);
058     * 07-Jan-2005 : Renamed getRangeExtent() --> findRangeBounds() (DG);
059     * 
060     */
061    
062    package org.jfree.chart.renderer.category;
063    
064    import java.awt.Graphics2D;
065    import java.awt.Polygon;
066    import java.awt.Shape;
067    import java.awt.geom.Rectangle2D;
068    import java.io.Serializable;
069    
070    import org.jfree.chart.axis.CategoryAxis;
071    import org.jfree.chart.axis.ValueAxis;
072    import org.jfree.chart.entity.EntityCollection;
073    import org.jfree.chart.plot.CategoryPlot;
074    import org.jfree.chart.plot.PlotOrientation;
075    import org.jfree.data.Range;
076    import org.jfree.data.category.CategoryDataset;
077    import org.jfree.data.general.DatasetUtilities;
078    import org.jfree.ui.RectangleEdge;
079    import org.jfree.util.PublicCloneable;
080    
081    /**
082     * A renderer that draws stacked area charts for a 
083     * {@link org.jfree.chart.plot.CategoryPlot}.
084     *
085     * @author Dan Rivett
086     */
087    public class StackedAreaRenderer extends AreaRenderer 
088                                     implements Cloneable, PublicCloneable, 
089                                                Serializable {
090    
091        /** For serialization. */
092        private static final long serialVersionUID = -3595635038460823663L;
093         
094        /**
095         * Creates a new renderer.
096         */
097        public StackedAreaRenderer() {
098            super();
099        }
100    
101        /**
102         * Returns the range of values the renderer requires to display all the 
103         * items from the specified dataset.
104         * 
105         * @param dataset  the dataset (<code>null</code> not permitted).
106         * 
107         * @return The range (or <code>null</code> if the dataset is empty).
108         */
109        public Range findRangeBounds(CategoryDataset dataset) {
110            return DatasetUtilities.findStackedRangeBounds(dataset);   
111        }
112    
113        /**
114         * Draw a single data item.
115         *
116         * @param g2  the graphics device.
117         * @param state  the renderer state.
118         * @param dataArea  the data plot area.
119         * @param plot  the plot.
120         * @param domainAxis  the domain axis.
121         * @param rangeAxis  the range axis.
122         * @param dataset  the data.
123         * @param row  the row index (zero-based).
124         * @param column  the column index (zero-based).
125         * @param pass  the pass index.
126         */
127        public void drawItem(Graphics2D g2,
128                             CategoryItemRendererState state,
129                             Rectangle2D dataArea,
130                             CategoryPlot plot,
131                             CategoryAxis domainAxis,
132                             ValueAxis rangeAxis,
133                             CategoryDataset dataset,
134                             int row,
135                             int column,
136                             int pass) {
137    
138            // plot non-null values...
139            Number value = dataset.getValue(row, column);
140            if (value == null) {
141                return;
142            }
143    
144            // leave the y values (y1, y0) untranslated as it is going to be be 
145            // stacked up later by previous series values, after this it will be 
146            // translated.
147            double x1 = domainAxis.getCategoryMiddle(
148                column, getColumnCount(), dataArea, plot.getDomainAxisEdge()
149            );
150            double y1 = 0.0;  // calculate later
151            double y1Untranslated = value.doubleValue();
152    
153            g2.setPaint(getItemPaint(row, column));
154            g2.setStroke(getItemStroke(row, column));
155    
156            if (column != 0) {
157    
158                Number previousValue = dataset.getValue(row, column - 1);
159                if (previousValue != null) {
160    
161                    double x0 = domainAxis.getCategoryMiddle(
162                        column - 1, getColumnCount(), dataArea, 
163                        plot.getDomainAxisEdge()
164                    );
165                    double y0Untranslated = previousValue.doubleValue();
166    
167                    // Get the previous height, but this will be different for both
168                    // y0 and y1 as the previous series values could differ.
169                    double previousHeightx0Untranslated 
170                        = getPreviousHeight(dataset, row, column - 1);
171                    double previousHeightx1Untranslated 
172                        = getPreviousHeight(dataset, row, column);
173    
174                    // Now stack the current y values on top of the previous values.
175                    y0Untranslated += previousHeightx0Untranslated;
176                    y1Untranslated += previousHeightx1Untranslated;
177    
178                    // Now translate the previous heights
179                    RectangleEdge location = plot.getRangeAxisEdge();
180                    double previousHeightx0 = rangeAxis.valueToJava2D(
181                        previousHeightx0Untranslated, dataArea, location
182                    );
183                    double previousHeightx1 = rangeAxis.valueToJava2D(
184                        previousHeightx1Untranslated, dataArea, location
185                    );
186    
187                    // Now translate the current y values.
188                    double y0 = rangeAxis.valueToJava2D(
189                        y0Untranslated, dataArea, location
190                    );
191                    y1 = rangeAxis.valueToJava2D(
192                        y1Untranslated, dataArea, location
193                    );
194    
195                    Polygon p = null;
196                    PlotOrientation orientation = plot.getOrientation();
197                    if (orientation == PlotOrientation.HORIZONTAL) {
198                        p = new Polygon();
199                        p.addPoint((int) y0, (int) x0);
200                        p.addPoint((int) y1, (int) x1);
201                        p.addPoint((int) previousHeightx1, (int) x1);
202                        p.addPoint((int) previousHeightx0, (int) x0);
203                    }
204                    else if (orientation == PlotOrientation.VERTICAL) {
205                        p = new Polygon();
206                        p.addPoint((int) x0, (int) y0);
207                        p.addPoint((int) x1, (int) y1);
208                        p.addPoint((int) x1, (int) previousHeightx1);
209                        p.addPoint((int) x0, (int) previousHeightx0);
210                    }
211                    g2.setPaint(getItemPaint(row, column));
212                    g2.setStroke(getItemStroke(row, column));
213                    g2.fill(p);
214                }
215    
216            }
217    
218            // add an item entity, if this information is being collected
219            EntityCollection entities = state.getEntityCollection();
220            if (entities != null) {
221                Shape shape = new Rectangle2D.Double(x1 - 3.0, y1 - 3.0, 6.0, 6.0);
222                addItemEntity(entities, dataset, row, column, shape);
223            }
224    
225        }
226    
227        /**
228         * Calculates the stacked value of the all series up to, but not including 
229         * <code>series</code> for the specified category, <code>category</code>.  
230         * It returns 0.0 if <code>series</code> is the first series, i.e. 0.
231         *
232         * @param data  the data.
233         * @param series  the series.
234         * @param category  the category.
235         *
236         * @return double returns a cumulative value for all series' values up to 
237         *         but excluding <code>series</code> for Object 
238         *         <code>category</code>.
239         */
240        protected double getPreviousHeight(CategoryDataset data, 
241                                           int series, int category) {
242    
243            double result = 0.0;
244            Number tmp;
245            for (int i = 0; i < series; i++) {
246                tmp = data.getValue(i, category);
247                if (tmp != null) {
248                    result += tmp.doubleValue();
249                }
250            }
251            return result;
252    
253        }
254    
255    }