001    /* ===========================================================
002     * JFreeChart : a free chart library for the Java(tm) platform
003     * ===========================================================
004     *
005     * (C) Copyright 2000-2007, 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     * AreaRenderer.java
029     * -----------------
030     * (C) Copyright 2002-2007, by Jon Iles and Contributors.
031     *
032     * Original Author:  Jon Iles;
033     * Contributor(s):   David Gilbert (for Object Refinery Limited);
034     *                   Christian W. Zuckschwerdt;
035     *
036     * $Id: AreaRenderer.java,v 1.6.2.9 2007/05/18 10:28:27 mungady Exp $
037     *
038     * Changes:
039     * --------
040     * 21-May-2002 : Version 1, contributed by John Iles (DG);
041     * 29-May-2002 : Now extends AbstractCategoryItemRenderer (DG);
042     * 11-Jun-2002 : Updated Javadoc comments (DG);
043     * 25-Jun-2002 : Removed unnecessary imports (DG);
044     * 01-Oct-2002 : Fixed errors reported by Checkstyle (DG);
045     * 10-Oct-2002 : Added constructors and basic entity support (DG);
046     * 24-Oct-2002 : Amendments for changes in CategoryDataset interface and 
047     *               CategoryToolTipGenerator interface (DG);
048     * 05-Nov-2002 : Replaced references to CategoryDataset with TableDataset (DG);
049     * 06-Nov-2002 : Renamed drawCategoryItem() --> drawItem() and now using axis 
050     *               for category spacing.  Renamed AreaCategoryItemRenderer 
051     *               --> AreaRenderer (DG);
052     * 17-Jan-2003 : Moved plot classes into a separate package (DG);
053     * 25-Mar-2003 : Implemented Serializable (DG);
054     * 10-Apr-2003 : Changed CategoryDataset to KeyedValues2DDataset in 
055     *               drawItem() method (DG);
056     * 12-May-2003 : Modified to take into account the plot orientation (DG);
057     * 30-Jul-2003 : Modified entity constructor (CZ);
058     * 13-Aug-2003 : Implemented Cloneable (DG);
059     * 07-Oct-2003 : Added renderer state (DG);
060     * 05-Nov-2004 : Modified drawItem() signature (DG);
061     * 20-Apr-2005 : Apply tooltips and URLs to legend items (DG);
062     * 09-Jun-2005 : Use addItemEntity() method from superclass (DG);
063     * ------------- JFREECHART 1.0.x ---------------------------------------------
064     * 11-Oct-2006 : Fixed bug in equals() method (DG);
065     * 30-Nov-2006 : Added checks for series visibility (DG);
066     * 20-Apr-2007 : Updated getLegendItem() for renderer change (DG);
067     * 17-May-2007 : Set datasetIndex and seriesIndex in getLegendItem() (DG);
068     * 18-May-2007 : Set dataset and seriesKey for LegendItem (DG);
069     * 
070     */
071    
072    package org.jfree.chart.renderer.category;
073    
074    import java.awt.Graphics2D;
075    import java.awt.Paint;
076    import java.awt.Shape;
077    import java.awt.Stroke;
078    import java.awt.geom.GeneralPath;
079    import java.awt.geom.Rectangle2D;
080    import java.io.Serializable;
081    
082    import org.jfree.chart.LegendItem;
083    import org.jfree.chart.axis.CategoryAxis;
084    import org.jfree.chart.axis.ValueAxis;
085    import org.jfree.chart.entity.EntityCollection;
086    import org.jfree.chart.event.RendererChangeEvent;
087    import org.jfree.chart.plot.CategoryPlot;
088    import org.jfree.chart.plot.PlotOrientation;
089    import org.jfree.chart.renderer.AreaRendererEndType;
090    import org.jfree.data.category.CategoryDataset;
091    import org.jfree.ui.RectangleEdge;
092    import org.jfree.util.PublicCloneable;
093    
094    /**
095     * A category item renderer that draws area charts.  You can use this renderer 
096     * with the {@link org.jfree.chart.plot.CategoryPlot} class.
097     */
098    public class AreaRenderer extends AbstractCategoryItemRenderer 
099                              implements Cloneable, PublicCloneable, Serializable {
100    
101        /** For serialization. */
102        private static final long serialVersionUID = -4231878281385812757L;
103        
104        /** A flag that controls how the ends of the areas are drawn. */
105        private AreaRendererEndType endType;
106        
107        /**
108         * Creates a new renderer.
109         */
110        public AreaRenderer() {
111            super();
112            this.endType = AreaRendererEndType.TAPER;
113        }
114    
115        /**
116         * Returns a token that controls how the renderer draws the end points.
117         * The default value is {@link AreaRendererEndType#TAPER}.
118         * 
119         * @return The end type (never <code>null</code>).
120         *
121         * @see #setEndType
122         */
123        public AreaRendererEndType getEndType() {
124            return this.endType;   
125        }
126        
127        /**
128         * Sets a token that controls how the renderer draws the end points, and 
129         * sends a {@link RendererChangeEvent} to all registered listeners.
130         * 
131         * @param type  the end type (<code>null</code> not permitted).
132         *
133         * @see #getEndType()
134         */
135        public void setEndType(AreaRendererEndType type) {
136            if (type == null) {
137                throw new IllegalArgumentException("Null 'type' argument.");   
138            }
139            this.endType = type;
140            notifyListeners(new RendererChangeEvent(this));
141        }
142        
143        /**
144         * Returns a legend item for a series.
145         *
146         * @param datasetIndex  the dataset index (zero-based).
147         * @param series  the series index (zero-based).
148         *
149         * @return The legend item.
150         */
151        public LegendItem getLegendItem(int datasetIndex, int series) {
152    
153            // if there is no plot, there is no dataset to access...
154            CategoryPlot cp = getPlot();
155            if (cp == null) {
156                return null;
157            }
158            
159            // check that a legend item needs to be displayed...
160            if (!isSeriesVisible(series) || !isSeriesVisibleInLegend(series)) {
161                return null;
162            }
163    
164            CategoryDataset dataset = cp.getDataset(datasetIndex);
165            String label = getLegendItemLabelGenerator().generateLabel(dataset, 
166                    series);
167            String description = label;
168            String toolTipText = null; 
169            if (getLegendItemToolTipGenerator() != null) {
170                toolTipText = getLegendItemToolTipGenerator().generateLabel(
171                        dataset, series);   
172            }
173            String urlText = null;
174            if (getLegendItemURLGenerator() != null) {
175                urlText = getLegendItemURLGenerator().generateLabel(dataset, 
176                        series);   
177            }
178            Shape shape = new Rectangle2D.Double(-4.0, -4.0, 8.0, 8.0);
179            Paint paint = lookupSeriesPaint(series);
180            Paint outlinePaint = lookupSeriesOutlinePaint(series);
181            Stroke outlineStroke = lookupSeriesOutlineStroke(series);
182    
183            LegendItem result = new LegendItem(label, description, toolTipText, 
184                    urlText, shape, paint, outlineStroke, outlinePaint);
185            result.setDataset(dataset);
186            result.setDatasetIndex(datasetIndex);
187            result.setSeriesKey(dataset.getRowKey(series));
188            result.setSeriesIndex(series);
189            return result;
190    
191        }
192    
193        /**
194         * Draw a single data item.
195         *
196         * @param g2  the graphics device.
197         * @param state  the renderer state.
198         * @param dataArea  the data plot area.
199         * @param plot  the plot.
200         * @param domainAxis  the domain axis.
201         * @param rangeAxis  the range axis.
202         * @param dataset  the dataset.
203         * @param row  the row index (zero-based).
204         * @param column  the column index (zero-based).
205         * @param pass  the pass index.
206         */
207        public void drawItem(Graphics2D g2,
208                             CategoryItemRendererState state,
209                             Rectangle2D dataArea,
210                             CategoryPlot plot,
211                             CategoryAxis domainAxis,
212                             ValueAxis rangeAxis,
213                             CategoryDataset dataset,
214                             int row,
215                             int column,
216                             int pass) {
217    
218            // do nothing if item is not visible
219            if (!getItemVisible(row, column)) {
220                return;   
221            }
222    
223            // plot non-null values only...
224            Number value = dataset.getValue(row, column);
225            if (value != null) {
226                PlotOrientation orientation = plot.getOrientation();
227                RectangleEdge axisEdge = plot.getDomainAxisEdge();
228                int count = dataset.getColumnCount();
229                float x0 = (float) domainAxis.getCategoryStart(column, count, 
230                        dataArea, axisEdge);
231                float x1 = (float) domainAxis.getCategoryMiddle(column, count, 
232                        dataArea, axisEdge);
233                float x2 = (float) domainAxis.getCategoryEnd(column, count, 
234                        dataArea, axisEdge);
235    
236                x0 = Math.round(x0);
237                x1 = Math.round(x1);
238                x2 = Math.round(x2);
239    
240                if (this.endType == AreaRendererEndType.TRUNCATE) {
241                    if (column == 0) {
242                        x0 = x1;   
243                    }
244                    else if (column == getColumnCount() - 1) {
245                        x2 = x1;   
246                    }
247                }
248                
249                double yy1 = value.doubleValue();
250    
251                double yy0 = 0.0;
252                if (column > 0) {
253                    Number n0 = dataset.getValue(row, column - 1);
254                    if (n0 != null) {
255                        yy0 = (n0.doubleValue() + yy1) / 2.0;
256                    }
257                }
258    
259                double yy2 = 0.0;
260                if (column < dataset.getColumnCount() - 1) {
261                    Number n2 = dataset.getValue(row, column + 1);
262                    if (n2 != null) {
263                        yy2 = (n2.doubleValue() + yy1) / 2.0;
264                    }
265                }
266    
267                RectangleEdge edge = plot.getRangeAxisEdge();
268                float y0 = (float) rangeAxis.valueToJava2D(yy0, dataArea, edge);
269                float y1 = (float) rangeAxis.valueToJava2D(yy1, dataArea, edge);
270                float y2 = (float) rangeAxis.valueToJava2D(yy2, dataArea, edge);
271                float yz = (float) rangeAxis.valueToJava2D(0.0, dataArea, edge);
272    
273                g2.setPaint(getItemPaint(row, column));
274                g2.setStroke(getItemStroke(row, column));
275    
276                GeneralPath area = new GeneralPath();
277    
278                if (orientation == PlotOrientation.VERTICAL) {
279                    area.moveTo(x0, yz);
280                    area.lineTo(x0, y0);
281                    area.lineTo(x1, y1);
282                    area.lineTo(x2, y2);
283                    area.lineTo(x2, yz);
284                }
285                else if (orientation == PlotOrientation.HORIZONTAL) {
286                    area.moveTo(yz, x0);
287                    area.lineTo(y0, x0);
288                    area.lineTo(y1, x1);
289                    area.lineTo(y2, x2);
290                    area.lineTo(yz, x2);
291                }
292                area.closePath();
293    
294                g2.setPaint(getItemPaint(row, column));
295                g2.fill(area);
296    
297                // draw the item labels if there are any...
298                if (isItemLabelVisible(row, column)) {
299                    drawItemLabel(g2, orientation, dataset, row, column, x1, y1, 
300                            (value.doubleValue() < 0.0));
301                }
302    
303                // add an item entity, if this information is being collected
304                EntityCollection entities = state.getEntityCollection();
305                if (entities != null) {
306                    addItemEntity(entities, dataset, row, column, area);
307                }
308            }
309    
310        }
311        
312        /**
313         * Tests this instance for equality with an arbitrary object.
314         *
315         * @param obj  the object to test (<code>null</code> permitted).
316         *
317         * @return A boolean.
318         */
319        public boolean equals(Object obj) {
320            if (obj == this) {    
321                return true;
322            }
323            if (!(obj instanceof AreaRenderer)) {
324                return false;
325            }
326            AreaRenderer that = (AreaRenderer) obj;
327            if (!this.endType.equals(that.endType)) {
328                return false;
329            }
330            return super.equals(obj);
331        }
332        
333        /**
334         * Returns an independent copy of the renderer.
335         * 
336         * @return A clone.
337         * 
338         * @throws CloneNotSupportedException  should not happen.
339         */
340        public Object clone() throws CloneNotSupportedException {
341            return super.clone();
342        }
343    
344    }