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